Overview

Background

During the summer of 2012, wild fires ravaged throughout the Algerian territory covering most of the northern part, especially the coastal cities. This disaster was due to the higher than average temperatures which reached as high as 50 degrees Celcius.

Objectives

One important measure against the reproduction of such disasters is the ability to predict their occurrence. Moreover, in this project, we will attempt to predict these forest fires based on multiple features related to weather indices.

Dataset Description

The Dataset we will use to train and test our models consists of 244 observations on two Algerian Wilayas (cities): Sidi-Bel Abbes and Bejaia. The observations have been gathered throughout the duration of 4 months from June to September 2012 for both cities.

The Dataset contains the following variables:

  1. Date: (DD/MM/YYYY) Day, month (‘june’ to ‘september’), year (2012)
  2. Temp: temperature noon (temperature max) in Celsius degrees: 22 to 42
  3. RH: Relative Humidity in %: 21 to 90
  4. Ws: Wind speed in km/h: 6 to 29
  5. Rain: total day in mm: 0 to 16.8
    FWI Components (check this LINK for more information)
  6. Fine Fuel Moisture Code (FFMC) index from the FWI system: 28.6 to 92.5
  7. Duff Moisture Code (DMC) index from the FWI system: 1.1 to 65.9
  8. Drought Code (DC) index from the FWI system: 7 to 220.4
  9. Initial Spread Index (ISI) index from the FWI system: 0 to 18.5
  10. Build-up Index (BUI) index from the FWI system: 1.1 to 68
  11. Fire Weather Index (FWI) Index: 0 to 31.1
  12. Classes: two classes, namely “fire” and “not fire”

Exploratory Data Analysis

We first start off by importing the necessary libraries for our analysis.

The libraries we used are the following:

  1. e1071: this package is used to perform statistic and probabilistic algorithms. In our case it is used to perform SVM
  2. MASS: This package includes many useful functions and data examples, including functions for estimating linear models through generalized least squares (GLS)
  3. plyr: contains tools for splitting, applying and combining data
  4. caret: a powrful library that has a train function which allows us to fit over 230 models uncluding tree-based models
  5. ROCR: a flexible tool for creating cutoff-parameterized 2D performance curves by freely combining two from over 25 performance measures
  6. pROC: a package specifically dedicated to ROC analysis
  7. randomForest: performs classification and regression on a forest of trees using random inputs
  8. gbm: short for generalized boosted models, provides extensions to Schapire’s AdaBoost algorithm
  9. dplyr: used for data manipulation providing a set of functions that are very useful
  10. tidyverse: contains multiple essensial tools and packages such as ggplot2 for visualization
  11. caTools: used for splitting our dataset into train/test sets

Importing the data

The Dataset provided to us was in the form of a .csv file that contained two tables, one table for the observations belonging to the Sidi-Bel Abbes region, and the other for Bejaia.

Before starting our analysis we separated the tables into two distinct files according to the region. We named both files Algerian_forest_fires_dataset_Bejaia.csv and Algerian_forest_fires_dataset_Sidi_Bel_Abbes.csv for Bejaia and Sidi-Bel Abbes respectively.

Cleaning and processing the data

We first check the existence of null values in the Dataset, none were found.

colSums(is.na(df_s))
        day       month        year Temperature          RH          Ws        Rain 
          0           0           0           0           0           0           0 
       FFMC         DMC          DC         ISI         BUI         FWI     Classes 
          0           0           0           0           0           0           0 

We then process to add a column in both datasets to indicate the region(Wilaya) in each table. We chose the following encoding:

  1. Bejaia = 0
  2. Sidi-Bel Abbes = 1

After that, we proceed to merge both our datasets into one single dataframe using full_join(), this will allow us to easily explore and analyze the data.

str(df)
'data.frame':   244 obs. of  15 variables:
 $ day        : int  1 2 3 4 5 6 7 8 9 10 ...
 $ month      : int  6 6 6 6 6 6 6 6 6 6 ...
 $ year       : int  2012 2012 2012 2012 2012 2012 2012 2012 2012 2012 ...
 $ Temperature: int  32 30 29 30 32 35 35 28 27 30 ...
 $ RH         : int  71 73 80 64 60 54 44 51 59 41 ...
 $ Ws         : int  12 13 14 14 14 11 17 17 18 15 ...
 $ Rain       : num  0.7 4 2 0 0.2 0.1 0.2 1.3 0.1 0 ...
 $ FFMC       : num  57.1 55.7 48.7 79.4 77.1 83.7 85.6 71.4 78.1 89.4 ...
 $ DMC        : num  2.5 2.7 2.2 5.2 6 8.4 9.9 7.7 8.5 13.3 ...
 $ DC         : num  8.2 7.8 7.6 15.4 17.6 26.3 28.9 7.4 14.7 22.5 ...
 $ ISI        : num  0.6 0.6 0.3 2.2 1.8 3.1 5.4 1.5 2.4 8.4 ...
 $ BUI        : num  2.8 2.9 2.6 5.6 6.5 9.3 10.7 7.3 8.3 13.1 ...
 $ FWI        : num  0.2 0.2 0.1 1 0.9 3.1 6 0.8 1.9 10 ...
 $ Classes    : chr  "not fire   " "not fire   " "not fire   " "not fire   " ...
 $ Region     : num  1 1 1 1 1 1 1 1 1 1 ...
unique(df$month)
[1] 6 7 8 9

We check again for any NA values that might have been introduced into the dataset by merging the data from both tables, we found out there was one row that contained NA value in DC and FWI. We delete that row since it will not affect our overall dataset.

dim(df)
[1] 243  15

We now proceed to display the different range of values some categorical variables might contain, mainly the Classes and the Region columns.

unique(df$Region)
[1] 1 0

We find that the Classes column has values that contain unneeded space characters, we proceed to trim those spaces.

df$Classes <- trimws(df$Classes, which = c("both"))
unique(df$Classes)
[1] "not fire" "fire"    

We then turn the fire/not fire values into 1/0 respectively for future analysis.

```{r}
Error: attempt to use zero-length variable name
str(df)
'data.frame':   243 obs. of  15 variables:
 $ day        : int  1 2 3 4 5 6 7 8 9 10 ...
 $ month      : int  6 6 6 6 6 6 6 6 6 6 ...
 $ year       : int  2012 2012 2012 2012 2012 2012 2012 2012 2012 2012 ...
 $ Temperature: int  32 30 29 30 32 35 35 28 27 30 ...
 $ RH         : int  71 73 80 64 60 54 44 51 59 41 ...
 $ Ws         : int  12 13 14 14 14 11 17 17 18 15 ...
 $ Rain       : num  0.7 4 2 0 0.2 0.1 0.2 1.3 0.1 0 ...
 $ FFMC       : num  57.1 55.7 48.7 79.4 77.1 83.7 85.6 71.4 78.1 89.4 ...
 $ DMC        : num  2.5 2.7 2.2 5.2 6 8.4 9.9 7.7 8.5 13.3 ...
 $ DC         : num  8.2 7.8 7.6 15.4 17.6 26.3 28.9 7.4 14.7 22.5 ...
 $ ISI        : num  0.6 0.6 0.3 2.2 1.8 3.1 5.4 1.5 2.4 8.4 ...
 $ BUI        : num  2.8 2.9 2.6 5.6 6.5 9.3 10.7 7.3 8.3 13.1 ...
 $ FWI        : num  0.2 0.2 0.1 1 0.9 3.1 6 0.8 1.9 10 ...
 $ Classes    : num  0 0 0 0 0 1 1 0 0 1 ...
 $ Region     : num  1 1 1 1 1 1 1 1 1 1 ...

We delete the year column since all observations were performed in the same year

str(df_scaled)
'data.frame':   243 obs. of  14 variables:
 $ day        : int  1 2 3 4 5 6 7 8 9 10 ...
 $ month      : int  6 6 6 6 6 6 6 6 6 6 ...
 $ Temperature: num  -0.042 -0.593 -0.869 -0.593 -0.042 ...
 $ RH         : num  0.604 0.739 1.211 0.132 -0.138 ...
 $ Ws         : num  -1.243 -0.887 -0.531 -0.531 -0.531 ...
 $ Rain       : num  -0.0314 1.6159 0.6175 -0.3809 -0.281 ...
 $ FFMC       : num  -1.4455 -1.5431 -2.0309 0.1085 -0.0517 ...
 $ DMC        : num  -0.983 -0.967 -1.007 -0.765 -0.7 ...
 $ DC         : num  -0.865 -0.873 -0.878 -0.714 -0.668 ...
 $ ISI        : num  -0.997 -0.997 -1.069 -0.612 -0.708 ...
 $ BUI        : num  -0.976 -0.969 -0.99 -0.779 -0.716 ...
 $ FWI        : num  -0.919 -0.919 -0.932 -0.811 -0.825 ...
 $ Classes    : num  0 0 0 0 0 1 1 0 0 1 ...
 $ Region     : num  1 1 1 1 1 1 1 1 1 1 ...

Visualizing the data

We have ended up with a clean and scaled dataframe named df_scaled, which we will use to visualize and further explore our data.

Our first instinct is to compare the two regions together in terms of number of fires, and average temperature.

We used the unscaled dataset to plot the real life values of the temperatures.

df %>%
  group_by(Region) %>%
  summarise(Region = Region, Number_of_fires = sum(Classes), Temperature = mean(Temperature)) %>%
  ggplot(aes(x=Region, y=Number_of_fires, fill = Temperature))+
  geom_col(position='dodge')
`summarise()` has grouped output by 'Region'. You can override using the `.groups` argument.

We can see that the the Sidi-Bel Abbes region has in total a greater number of fires and a higher average temperature throughout the summer of 2012.

Further Analysis

Correlation Matrix

The previous results push us to suspect a positive relationship between the temperature and the likelihood of having a fire. However, we need to investigate all the other variables, which is why we will plot a correlation matrix of the features in the dataset.

Feature Selection

We performed feature selection using the Caret package to determine which features are the most important and which are the least.

In this case, we opted for Linear Discriminant Analysis with Stepwise Feature Selection by specifying stepLDA as our method.

The varImp function returns a measure of importance out of 100 for each of the features. According to the official Caret documentation, the importance metric is calculated by conducting a ROC curve analysis on each predictor; a series of cutoffs is applied to the predictor data to predict the class. The AUC is then computed and is used as a measure of variable importance.

We can see that the variables month, Ws, Region, and day are insignificant compared to other features. We will disregard them in our model. To determine this we used a threshold of 0.7 for the importance measure.

Model Building

For the following models, we will only use the features that were the most significant in our feature selection phase. The selected features are:

  1. Temperature
  2. Rain
  3. FFMC
  4. DMC
  5. DC
  6. ISI
  7. BUI
  8. FWI
  9. RH

Splitting the dataset

We begin by splitting the data into train/test sets with a 80/20 split. This split was chosen by default as a good practice. This will leave us with 191 observations in the training set as well as 52 in the test set. Due to the small nature of the dataset at hand we will later apply cross validation to some models in order to further examine their performance and compare them with each other.

We set a seed of 1000

dim(test_set)
[1] 52 14

Logistic Regression

Logistic Regression is considered to be an extension of Linear Regression, in which we predict the qualitative response for an observation. It gives us the probability of a certain observation belonging to a class in binomial classification, but can also be extended to be used for multiple classifications.

Training the model

We first start by fitting our model on the training set. As we do that we get an error that our model did not converge, this is due to our model being able to perfectly split the dataset into positive/negative observations. This might soud counterintuitive but this error is a good sign.

logistic_model

Call:  glm(formula = Classes ~ Temperature + Rain + FFMC + DMC + DC + 
    ISI + BUI + FWI + RH, family = "binomial", data = train_set)

Coefficients:
(Intercept)  Temperature         Rain         FFMC          DMC           DC          ISI          BUI          FWI           RH  
     195.74       -42.31        50.32       114.50       -57.81        65.45       301.95       -38.43       155.10       -17.28  

Degrees of Freedom: 190 Total (i.e. Null);  181 Residual
Null Deviance:      261.5 
Residual Deviance: 1.428e-07    AIC: 20

Testing the model

Since logistic regression gives us the probability of each observation belonging to the 1 class, we will use a 0.5 threshold to transform that probability into a classification of either 0 or 1.

After getting our predictions, we will use the confusion matrix function from the caret library that computes a set of performance matrices including f1-score, recall and precision. Other matrices computed include: sensitivity, specificity, prevalence etc. The official documentation for this function and the formulas for all matrices are found in this link. We will only be interested in the f1-score, recall, precision, accuracy and balanced accuracy.

On the train set

Our model gives us an accuracy and an f1 score of 100% on the training set.

train_set$Classes
  [1] 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0 0 1 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 0 0 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 1 0 0 1 0 0 1 1
 [83] 1 1 1 1 1 0 1 0 0 1 0 0 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 0 1 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 1 1 1 1 1 0 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[165] 1 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0

On the test set

On the test set hovewer, we get an accuracy of 98.08% and an f1 score of 98.25%.

test_set$Classes
 [1] 0 1 0 0 1 1 0 0 1 1 0 0 1 1 1 1 1 1 1 0 1 1 1 1 1 0 0 1 0 0 1 0 0 1 0 1 1 1 1 0
[41] 0 1 1 1 1 0 0 0 0 0 0 1
Levels: 0 1

Plotting the ROC curve

As we plot the ROC curve, we can see that the AUC is equal to 98.27586% which is almost a perfect classifier.

LDA

Linear Discriminant Analysis is best used when the decision boundary of our given dataset is assumed to be linear. There are two basic assumptions that LDA takes into consideration:

  1. There is a common variance across all response classes
  2. The distribution of observations in each response class is normal with a class-specific mean, and a common variance

Since LDA assumes that each input variable has the same variance, we will use the standardized data-frame in the train test splits. Each variable in the standardized data-frame has mean of 0 and variance of 1.

Training the model

lda_model
Call:
lda(Classes ~ Temperature + Rain + FFMC + DMC + DC + ISI + BUI + 
    FWI + RH, data = train_set, family = "binomial")

Prior probabilities of groups:
       0        1 
0.434555 0.565445 

Group means:
  Temperature       Rain       FFMC        DMC         DC        ISI        BUI        FWI         RH
0  -0.6330807  0.3721387 -0.8343419 -0.6612676 -0.5799594 -0.8280654 -0.6669278 -0.8161663  0.4701120
1   0.4633549 -0.3244797  0.6739891  0.4867494  0.4267318  0.6591273  0.4850895  0.6262935 -0.3699445

Coefficients of linear discriminants:
                    LD1
Temperature  0.08978603
Rain         0.18218660
FFMC         1.35842928
DMC         -1.15810129
DC          -0.32744874
ISI          0.37584652
BUI          1.09904453
FWI          0.92679809
RH           0.57487142

Testing the model

On the train set

On our training data, the model reached an accuracy of 95.81% and an f1 score of 96.30%, with 4 false positives and 1 false negative.

confusionMatrix(preds_lda$class, train_set$Classes,
                mode = "everything",
                positive="1")
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0  79   4
         1   4 104
                                          
               Accuracy : 0.9581          
                 95% CI : (0.9191, 0.9817)
    No Information Rate : 0.5654          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.9148          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.9630          
            Specificity : 0.9518          
         Pos Pred Value : 0.9630          
         Neg Pred Value : 0.9518          
              Precision : 0.9630          
                 Recall : 0.9630          
                     F1 : 0.9630          
             Prevalence : 0.5654          
         Detection Rate : 0.5445          
   Detection Prevalence : 0.5654          
      Balanced Accuracy : 0.9574          
                                          
       'Positive' Class : 1               
                                          

On the test set

As we can see below, our the number of false positives is 1, and the number of false negatives is 1 as well. Our model also yielded an accuracy of 96.15% and an f1 score of 96.55%.

confusionMatrix(preds_lda$class, test_set$Classes,
                mode = "everything",
                positive="1")
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 22  1
         1  1 28
                                          
               Accuracy : 0.9615          
                 95% CI : (0.8679, 0.9953)
    No Information Rate : 0.5577          
    P-Value [Acc > NIR] : 5.691e-11       
                                          
                  Kappa : 0.922           
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.9655          
            Specificity : 0.9565          
         Pos Pred Value : 0.9655          
         Neg Pred Value : 0.9565          
              Precision : 0.9655          
                 Recall : 0.9655          
                     F1 : 0.9655          
             Prevalence : 0.5577          
         Detection Rate : 0.5385          
   Detection Prevalence : 0.5577          
      Balanced Accuracy : 0.9610          
                                          
       'Positive' Class : 1               
                                          

Potting the ROC curve

The AUC for LDA was 96.10%, similar to the one for Logistic Regression.

QDA

Quadratic Discriminant Analysis is best used when the decision boundary of our given dataset is assumed to be non-linear. Similarly to LDA, QDA makes two basic assumptions:

  1. There is a different covariance for each of the response classes
  2. The distribution of observations in each response class is normal with a class-specific mean, and a class-specific covariance

Training the model

qda_model
Call:
qda(Classes ~ Temperature + Rain + FFMC + DMC + DC + ISI + BUI + 
    FWI + RH, data = train_set)

Prior probabilities of groups:
       0        1 
0.434555 0.565445 

Group means:
  Temperature       Rain       FFMC        DMC         DC        ISI        BUI        FWI         RH
0  -0.6330807  0.3721387 -0.8343419 -0.6612676 -0.5799594 -0.8280654 -0.6669278 -0.8161663  0.4701120
1   0.4633549 -0.3244797  0.6739891  0.4867494  0.4267318  0.6591273  0.4850895  0.6262935 -0.3699445

[Interpretation on the coefficients]

Testing the model

On the train set

Our model yields an accuracy of 98.43% and an f1 score of 98.62% on the training set.

confusionMatrix(preds_qda$class, train_set$Classes,
                mode = "everything",
                positive="1")
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0  81   1
         1   2 107
                                          
               Accuracy : 0.9843          
                 95% CI : (0.9548, 0.9967)
    No Information Rate : 0.5654          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.968           
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.9907          
            Specificity : 0.9759          
         Pos Pred Value : 0.9817          
         Neg Pred Value : 0.9878          
              Precision : 0.9817          
                 Recall : 0.9907          
                     F1 : 0.9862          
             Prevalence : 0.5654          
         Detection Rate : 0.5602          
   Detection Prevalence : 0.5707          
      Balanced Accuracy : 0.9833          
                                          
       'Positive' Class : 1               
                                          
On the test set

As we can see below, our the number of false positives is 1, and the number of false negatives is 1. The results are very good but the other way around would have been better as we do not want to miss any positives meaning we want to predict all fires. Our model yielded an f1-score of 93.33% and an accuracy of 92.31%.

confusionMatrix(preds_qda$class, test_set$Classes,
                mode = "everything",
                positive="1")
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 20  1
         1  3 28
                                          
               Accuracy : 0.9231          
                 95% CI : (0.8146, 0.9786)
    No Information Rate : 0.5577          
    P-Value [Acc > NIR] : 7.729e-09       
                                          
                  Kappa : 0.8427          
                                          
 Mcnemar's Test P-Value : 0.6171          
                                          
            Sensitivity : 0.9655          
            Specificity : 0.8696          
         Pos Pred Value : 0.9032          
         Neg Pred Value : 0.9524          
              Precision : 0.9032          
                 Recall : 0.9655          
                     F1 : 0.9333          
             Prevalence : 0.5577          
         Detection Rate : 0.5385          
   Detection Prevalence : 0.5962          
      Balanced Accuracy : 0.9175          
                                          
       'Positive' Class : 1               
                                          

Potting the ROC curve

After plotting the ROC curve we got an AUC of 91.75%, which is worse than both logistic regression and LDA.

We can observe that QDA performs better than LDA on the training data, because it has the tendency to over-fit it. However, LDA performs better on the testing data since it generalizes better on unseen data points.

KNN Classifier

In this section, we will explore KNN’s performance on our problem. We will use hyper parameter tuning to determine the best number of nearest numbers (K) and we will also use repeated cross validation in our training for better performance estimation.

Since KNN is a distance based model, we will here again use our normalized dataset instead of the original.

Training the model

Setting up the Cross-Validation for Hyperparameter tuning

The summaryFunction argument determines which metric to use to determine the performance of a particular hyperparameter setting. Here we shall use defaultSummary which calculates accuracy and kappa statistic.

We have opted to go with the repeated 10 fold cross-validation method repeated 10 times. ClassProbs parameter is set to TRUE and we can set the threshold later when we test our model performance.

training_control <- trainControl(method = "repeatedcv",
                                 summaryFunction = defaultSummary,
                                 classProbs = TRUE,
                                 number = 10,
                                 repeats = 10)
Training with Cross-validation

Now we use the train() function to perform the model training/tuning of the k hyper-parameter. The range of k is from 3 to 85 in steps of 2 meaning we will only have odd values of k only as it is best practice for the KNN clustering.

Another tweak that we need to make on our data-set is to change our target variable values to valid R variable names in order for the KNN algorithm to work with class Probabilities as each values of our target variable will become a variable with its own probability values. Leaving the values as {0,1} will throw an error at us, therefore we will set our Classes variable values back to ‘fire’ and ‘not_fire’ and proceed.

train_set_names$Classes
  [1] not_fire not_fire not_fire not_fire fire     fire     not_fire not_fire fire     fire     not_fire not_fire not_fire not_fire not_fire not_fire fire     not_fire
 [19] not_fire fire     fire     fire     fire     not_fire fire     fire     fire     fire     fire     fire     fire     fire     not_fire fire     fire     fire    
 [37] fire     fire     fire     fire     fire     fire     not_fire not_fire fire     fire     fire     not_fire fire     fire     fire     fire     not_fire not_fire
 [55] fire     fire     fire     fire     fire     fire     fire     fire     fire     fire     fire     fire     fire     fire     not_fire fire     fire     fire    
 [73] not_fire not_fire fire     not_fire not_fire fire     not_fire not_fire fire     fire     fire     fire     fire     fire     fire     not_fire fire     not_fire
 [91] not_fire fire     not_fire not_fire not_fire not_fire not_fire not_fire not_fire fire     fire     not_fire not_fire fire     fire     not_fire not_fire not_fire
[109] not_fire not_fire not_fire not_fire fire     fire     fire     fire     fire     fire     not_fire fire     not_fire not_fire fire     not_fire fire     not_fire
[127] not_fire not_fire not_fire not_fire not_fire not_fire fire     fire     fire     not_fire not_fire not_fire fire     fire     fire     fire     fire     not_fire
[145] not_fire fire     fire     fire     fire     not_fire fire     fire     fire     fire     fire     fire     fire     fire     fire     fire     fire     fire    
[163] fire     fire     fire     fire     not_fire not_fire not_fire not_fire not_fire fire     not_fire not_fire not_fire not_fire not_fire not_fire not_fire not_fire
[181] fire     fire     fire     fire     fire     not_fire not_fire not_fire not_fire not_fire not_fire
Levels: not_fire fire
knn_cv
k-Nearest Neighbors 

191 samples
  9 predictor
  2 classes: 'not_fire', 'fire' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 10 times) 
Summary of sample sizes: 172, 172, 172, 172, 172, 172, ... 
Resampling results across tuning parameters:

  k   Accuracy   Kappa    
   3  0.9271520  0.8527382
   5  0.9274912  0.8522411
   7  0.9432690  0.8849233
   9  0.9358684  0.8694234
  11  0.9326520  0.8624502
  13  0.9331550  0.8633457
  15  0.9289971  0.8548642
  17  0.9258918  0.8484631
  19  0.9243392  0.8453284
  21  0.9259152  0.8488081
  23  0.9223070  0.8411710
  25  0.9222310  0.8407475
  27  0.9253333  0.8470459
  29  0.9290468  0.8548415
  31  0.9269415  0.8502645
  33  0.9285263  0.8534166
  35  0.9316316  0.8598659
  37  0.9332105  0.8632230
  39  0.9347632  0.8663944
  41  0.9342368  0.8652574
  43  0.9368684  0.8708486
  45  0.9379211  0.8729864
  47  0.9342895  0.8655254
  49  0.9358158  0.8686271
  51  0.9368129  0.8708728
  53  0.9379240  0.8732183
  55  0.9374240  0.8721588
  57  0.9373977  0.8720978
  59  0.9410292  0.8795798
  61  0.9432164  0.8842024
  63  0.9447427  0.8873647
  65  0.9462690  0.8905616
  67  0.9457427  0.8894259
  69  0.9447690  0.8874640
  71  0.9395292  0.8768961
  73  0.9369240  0.8717375
  75  0.9353158  0.8684827
  77  0.9321813  0.8623134
  79  0.9296023  0.8570235
  81  0.9254415  0.8486104
  83  0.9232573  0.8443844
  85  0.9185965  0.8350012

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was k = 65.
Distribution of predicted probabilites -threshold inspection-

Inspecting the probabilities reveals that a cutoff probability around 0.5 gives the best classification results. In the function predict the cutoff is set to be 0.5 by default which means we do not need to change it.

Testing the model

When testing our model on the test set however, we get an accuracy of 86.54% and an f1 score of 87.72%

confusionMatrix(preds_knn, test_set_knn$Classes,
                mode = "everything",
                positive="fire")
Confusion Matrix and Statistics

          Reference
Prediction not_fire fire
  not_fire       20    4
  fire            3   25
                                          
               Accuracy : 0.8654          
                 95% CI : (0.7421, 0.9441)
    No Information Rate : 0.5577          
    P-Value [Acc > NIR] : 2.105e-06       
                                          
                  Kappa : 0.7284          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.8621          
            Specificity : 0.8696          
         Pos Pred Value : 0.8929          
         Neg Pred Value : 0.8333          
              Precision : 0.8929          
                 Recall : 0.8621          
                     F1 : 0.8772          
             Prevalence : 0.5577          
         Detection Rate : 0.4808          
   Detection Prevalence : 0.5385          
      Balanced Accuracy : 0.8658          
                                          
       'Positive' Class : fire            
                                          

Potting the ROC curve

After plotting the ROC curve, we get an AUC of 86.58% which is the worst out of all of our models so far.

auc 
[1] 0.8658171

Ensemble Methods / Tree based methods

Simple Decision Trees and tree pruning

The goal of ensemble modeling is to improve performance over a baseline model by combining multiple models. So, we will set the baseline performance measure by starting with one algorithm. In our case, we will build a simple decision tree.

Decision trees are widely used classifiers in industries based on their transparency in describing rules that lead to a prediction. They are arranged in a hierarchical tree-like structure and are simple to understand and interpret. They are not susceptible to outliers and are able to capture nonlinear relationships.

We will be using the rpart library for creating decision trees. (rpart) stands for recursive partitioning and employs the CART (classification and regression trees) algorithm. Apart from the rpart library, there are many other decision tree libraries like C50, Party, Tree, and mapTree.

library(rpart.plot)

Training the model without the pruning

Next, we create a decision tree model by calling the rpart function. Let’s first create a base model with default parameters and value. Notice that we do not include any train control meaning that we are not using any bagging, cross validation or pruning techniques. The resulting tree is a simple decision tree. We will explore the performance of the model on the train and test sets next.

base_model <- rpart(Classes ~ Temperature+Rain+FFMC+DMC+DC+ISI+BUI+FWI+RH, data = train_set, method = "class")
summary(base_model)
Call:
rpart(formula = Classes ~ Temperature + Rain + FFMC + DMC + DC + 
    ISI + BUI + FWI + RH, data = train_set, method = "class")
  n= 191 

         CP nsplit  rel error     xerror       xstd
1 0.9638554      0 1.00000000 1.00000000 0.08253842
2 0.0100000      1 0.03614458 0.09638554 0.03335614

Variable importance
FFMC  ISI  FWI  DMC  BUI   DC 
  21   20   18   14   14   13 

Node number 1: 191 observations,    complexity param=0.9638554
  predicted class=1  expected loss=0.434555  P(node) =1
    class counts:    83   108
   probabilities: 0.435 0.565 
  left son=2 (80 obs) right son=3 (111 obs)
  Primary splits:
      FFMC < 0.1608133  to the left,  improve=88.02604, (0 missing)
      ISI  < -0.5036757 to the left,  improve=88.02604, (0 missing)
      FWI  < -0.4281113 to the left,  improve=75.38051, (0 missing)
      DMC  < -0.5269618 to the left,  improve=50.85939, (0 missing)
      BUI  < -0.6424139 to the left,  improve=46.48449, (0 missing)
  Surrogate splits:
      ISI < -0.5518194 to the left,  agree=0.990, adj=0.975, (0 split)
      FWI < -0.5557897 to the left,  agree=0.942, adj=0.862, (0 split)
      DMC < -0.5390654 to the left,  agree=0.864, adj=0.675, (0 split)
      BUI < -0.6424139 to the left,  agree=0.853, adj=0.650, (0 split)
      DC  < -0.6656973 to the left,  agree=0.848, adj=0.638, (0 split)

Node number 2: 80 observations
  predicted class=0  expected loss=0  P(node) =0.4188482
    class counts:    80     0
   probabilities: 1.000 0.000 

Node number 3: 111 observations
  predicted class=1  expected loss=0.02702703  P(node) =0.5811518
    class counts:     3   108
   probabilities: 0.027 0.973 
#Plot Decision Tree
rpart.plot(base_model)

After exploring the confusion matrix and the different performance metrics, we can see that our base decision tree does not fit the data perfectly and has 3 miss-classifications on the training set. Those 3 false positives caused the model’s accuracy to be 98.43% and the f1-score to be 98.63%.

confusionMatrix(preds_tree, train_set$Classes, mode = "everything", positive='1')
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0  80   0
         1   3 108
                                          
               Accuracy : 0.9843          
                 95% CI : (0.9548, 0.9967)
    No Information Rate : 0.5654          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.9679          
                                          
 Mcnemar's Test P-Value : 0.2482          
                                          
            Sensitivity : 1.0000          
            Specificity : 0.9639          
         Pos Pred Value : 0.9730          
         Neg Pred Value : 1.0000          
              Precision : 0.9730          
                 Recall : 1.0000          
                     F1 : 0.9863          
             Prevalence : 0.5654          
         Detection Rate : 0.5654          
   Detection Prevalence : 0.5812          
      Balanced Accuracy : 0.9819          
                                          
       'Positive' Class : 1               
                                          

Testing the unpruned model

Our base decision tree performs very well on unseen data with just one false positive, an accuracy of 98% and an f1-score of 98.31%.

base_model
n= 191 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

1) root 191 83 1 (0.43455497 0.56544503)  
  2) FFMC< 0.1608133 80  0 0 (1.00000000 0.00000000) *
  3) FFMC>=0.1608133 111  3 1 (0.02702703 0.97297297) *

ROC curve for unpruned model

This model gives us an AUC of 97.82%

Training the model with pruning

Pre-pruning is also known as early stopping criteria. As the name suggests, the criteria are set as parameter values while building the rpart model. Below are some of the pre-pruning criteria that can be used. The tree stops growing when it meets any of these pre-pruning criteria, or it discovers the pure classes.

The complexity parameter (cp) in rpart is the minimum improvement in the model needed at each node. It is based on the cost complexity of the model and works as follows:

  • For the given tree, add up the missclassification at every terminal node.
  • Then multiply the number of splits time a penalty term (lambda) and add it to the total missclassification.
  • The lambda is determined through cross-validation and not reported in R.
  • The cp we see using printcp() is the scaled version of lambda over the misclassifcation rate of the overall data.

The cp value is a stopping parameter. It helps speed up the search for splits because it can identify splits that donít meet this criteria and prune them before going too far.

Other parameters include but are not limited to:

  • maxdepth: This parameter is used to set the maximum depth of a tree. In this prepruning step.

  • minsplit: It is the minimum number of records that must exist in a node for a split to happen or be attempted.

And one last thing, since we are in a classification setting, we have to specify class as the method used for building our tree instead of ‘anova’ that is used in regression settings.

pruned_base_model <- rpart(Classes ~ Temperature+Rain+FFMC+DMC+DC+ISI+BUI+FWI+RH, data = train_set, method = "class",  control = rpart.control(cp = 0, maxdepth = 8, minsplit = 8))
summary(pruned_base_model)
Call:
rpart(formula = Classes ~ Temperature + Rain + FFMC + DMC + DC + 
    ISI + BUI + FWI + RH, data = train_set, method = "class", 
    control = rpart.control(cp = 0, maxdepth = 8, minsplit = 8))
  n= 191 

          CP nsplit  rel error     xerror       xstd
1 0.96385542      0 1.00000000 1.00000000 0.08253842
2 0.01204819      1 0.03614458 0.09638554 0.03335614
3 0.00000000      2 0.02409639 0.09638554 0.03335614

Variable importance
 ISI FFMC  FWI  DMC  BUI   DC 
  21   21   18   14   13   13 

Node number 1: 191 observations,    complexity param=0.9638554
  predicted class=1  expected loss=0.434555  P(node) =1
    class counts:    83   108
   probabilities: 0.435 0.565 
  left son=2 (80 obs) right son=3 (111 obs)
  Primary splits:
      FFMC < 0.1608133  to the left,  improve=88.02604, (0 missing)
      ISI  < -0.5036757 to the left,  improve=88.02604, (0 missing)
      FWI  < -0.4281113 to the left,  improve=75.38051, (0 missing)
      DMC  < -0.5269618 to the left,  improve=50.85939, (0 missing)
      BUI  < -0.6424139 to the left,  improve=46.48449, (0 missing)
  Surrogate splits:
      ISI < -0.5518194 to the left,  agree=0.990, adj=0.975, (0 split)
      FWI < -0.5557897 to the left,  agree=0.942, adj=0.862, (0 split)
      DMC < -0.5390654 to the left,  agree=0.864, adj=0.675, (0 split)
      BUI < -0.6424139 to the left,  agree=0.853, adj=0.650, (0 split)
      DC  < -0.6656973 to the left,  agree=0.848, adj=0.638, (0 split)

Node number 2: 80 observations
  predicted class=0  expected loss=0  P(node) =0.4188482
    class counts:    80     0
   probabilities: 1.000 0.000 

Node number 3: 111 observations,    complexity param=0.01204819
  predicted class=1  expected loss=0.02702703  P(node) =0.5811518
    class counts:     3   108
   probabilities: 0.027 0.973 
  left son=6 (3 obs) right son=7 (108 obs)
  Primary splits:
      ISI  < -0.4796039 to the left,  improve=2.5230230, (0 missing)
      FWI  < -0.6297088 to the left,  improve=2.5230230, (0 missing)
      FFMC < 0.2967052  to the left,  improve=2.0878380, (0 missing)
      DMC  < -0.3978571 to the left,  improve=0.9628378, (0 missing)
      DC   < -0.6331791 to the left,  improve=0.8572553, (0 missing)
  Surrogate splits:
      FWI < -0.6297088 to the left,  agree=0.982, adj=0.333, (0 split)

Node number 6: 3 observations
  predicted class=0  expected loss=0.3333333  P(node) =0.01570681
    class counts:     2     1
   probabilities: 0.667 0.333 

Node number 7: 108 observations
  predicted class=1  expected loss=0.009259259  P(node) =0.565445
    class counts:     1   107
   probabilities: 0.009 0.991 
#Plot Decision Tree
printcp(pruned_base_model)

Classification tree:
rpart(formula = Classes ~ Temperature + Rain + FFMC + DMC + DC + 
    ISI + BUI + FWI + RH, data = train_set, method = "class", 
    control = rpart.control(cp = 0, maxdepth = 8, minsplit = 8))

Variables actually used in tree construction:
[1] FFMC ISI 

Root node error: 83/191 = 0.43455

n= 191 

        CP nsplit rel error   xerror     xstd
1 0.963855      0  1.000000 1.000000 0.082538
2 0.012048      1  0.036145 0.096386 0.033356
3 0.000000      2  0.024096 0.096386 0.033356
rpart.plot(pruned_base_model)

The summary of our base model will give us the details of each split with the number of observations, the value of the complexity parameter, the predicted class, the class counts with their probabilities and the children of the node. It will also give details about the future splits starting with the primary splits that will follow and the percent improvement in the prediction as well as the surrogate splits that come later on.

The resulting tree as explained in the above section, is the smallest tree with the lowest miss-classification loss. This tree is plotted with the split details and leaf node classes.

The optimal CP value found was 0.012048

confusionMatrix(preds, train_set$Classes,
                mode = "everything",
                positive='1')
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0  82   1
         1   1 107
                                          
               Accuracy : 0.9895          
                 95% CI : (0.9627, 0.9987)
    No Information Rate : 0.5654          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.9787          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.9907          
            Specificity : 0.9880          
         Pos Pred Value : 0.9907          
         Neg Pred Value : 0.9880          
              Precision : 0.9907          
                 Recall : 0.9907          
                     F1 : 0.9907          
             Prevalence : 0.5654          
         Detection Rate : 0.5602          
   Detection Prevalence : 0.5654          
      Balanced Accuracy : 0.9893          
                                          
       'Positive' Class : 1               
                                          

The train accuracy of our tree is 98.95% with an f1 score of 99% as well with a total of 2 missclassifications, 1 FP and 1 FN.

Testing the pruned model

We have a 96.15% accuracy on our held-out validation set which means we have successfully avoided over-fitting using tree pruning.

confusionMatrix(preds, test_set$Classes,
                mode = "everything",
                positive='1')
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 23  2
         1  0 27
                                          
               Accuracy : 0.9615          
                 95% CI : (0.8679, 0.9953)
    No Information Rate : 0.5577          
    P-Value [Acc > NIR] : 5.691e-11       
                                          
                  Kappa : 0.9227          
                                          
 Mcnemar's Test P-Value : 0.4795          
                                          
            Sensitivity : 0.9310          
            Specificity : 1.0000          
         Pos Pred Value : 1.0000          
         Neg Pred Value : 0.9200          
              Precision : 1.0000          
                 Recall : 0.9310          
                     F1 : 0.9643          
             Prevalence : 0.5577          
         Detection Rate : 0.5192          
   Detection Prevalence : 0.5192          
      Balanced Accuracy : 0.9655          
                                          
       'Positive' Class : 1               
                                          
ROC curve for pruned model

The AUC was 96.55%

BAGGING

Bagging, or bootstrap aggregation, is an ensemble method that involves training the same algorithm many times by using different subsets sampled from the training data. The final output prediction is then averaged across the predictions of all the sub-models. The two most popular bagging ensemble techniques are Bagged Decision Trees and Random Forest.

Bagged Decision Trees

This method performs best with algorithms that have high variance. The argument method=“treebag” specifies the algorithm. We will train our model using a 5-fold cross validation repeated 5 times. The sampling strategy used for the bagged trees is ROSE.

Training the model
Training accuracy

We achieved a perfect fit using bagged trees trained using a 5-fold CV repeated 5 times.

confusionMatrix(preds, train_set$Classes,
                mode = "everything",
                positive='1')
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0  83   0
         1   0 108
                                     
               Accuracy : 1          
                 95% CI : (0.9809, 1)
    No Information Rate : 0.5654     
    P-Value [Acc > NIR] : < 2.2e-16  
                                     
                  Kappa : 1          
                                     
 Mcnemar's Test P-Value : NA         
                                     
            Sensitivity : 1.0000     
            Specificity : 1.0000     
         Pos Pred Value : 1.0000     
         Neg Pred Value : 1.0000     
              Precision : 1.0000     
                 Recall : 1.0000     
                     F1 : 1.0000     
             Prevalence : 0.5654     
         Detection Rate : 0.5654     
   Detection Prevalence : 0.5654     
      Balanced Accuracy : 1.0000     
                                     
       'Positive' Class : 1          
                                     
Testing accuracy

The bagged model did not achieve a perfect performance on unseen data which leads us to believe it over fit the data. This can be caused by the fact that the bagged trees were highly correlated between each other which could be due to the absence of randomization in the features used for each bagged tree. What happened most probably is the use of the same strong predictors in all bagged trees causing this high correlation between them. In order to get rid of it we will implement Random Forests next which adds this randomization in the features selected for each bagged tree. The accuracy we got is 98%

confusionMatrix(preds, as.factor(test_set$Classes),
                mode = "everything",
                positive='1')
Confusion Matrix and Statistics

          Reference
Prediction  1  2
         1 23  1
         2  0 28
                                          
               Accuracy : 0.9808          
                 95% CI : (0.8974, 0.9995)
    No Information Rate : 0.5577          
    P-Value [Acc > NIR] : 2.743e-12       
                                          
                  Kappa : 0.9612          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 1.0000          
            Specificity : 0.9655          
         Pos Pred Value : 0.9583          
         Neg Pred Value : 1.0000          
              Precision : 0.9583          
                 Recall : 1.0000          
                     F1 : 0.9787          
             Prevalence : 0.4423          
         Detection Rate : 0.4423          
   Detection Prevalence : 0.4615          
      Balanced Accuracy : 0.9828          
                                          
       'Positive' Class : 1               
                                          

Random Forests

Random Forest is an extension of bagged decision trees, where in addition to sampling the data, we also sample the variables in each bagged decision tree. The trees are constructed with the objective of reducing the correlation between the individual decision trees by making sure we do not use the same strong predictors in all bagged trees resulting in strongly correlated trees.

Training the model

Training accuracy

Once again, our random forest model achieved a perfect first with a 5-fold CV repeated 5 times.

confusionMatrix(preds, train_set$Classes,
                mode = "everything",
                positive='1')
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0  83   0
         1   0 108
                                     
               Accuracy : 1          
                 95% CI : (0.9809, 1)
    No Information Rate : 0.5654     
    P-Value [Acc > NIR] : < 2.2e-16  
                                     
                  Kappa : 1          
                                     
 Mcnemar's Test P-Value : NA         
                                     
            Sensitivity : 1.0000     
            Specificity : 1.0000     
         Pos Pred Value : 1.0000     
         Neg Pred Value : 1.0000     
              Precision : 1.0000     
                 Recall : 1.0000     
                     F1 : 1.0000     
             Prevalence : 0.5654     
         Detection Rate : 0.5654     
   Detection Prevalence : 0.5654     
      Balanced Accuracy : 1.0000     
                                     
       'Positive' Class : 1          
                                     

Testing accuracy

On our dataset, the random forest did not perform better that the previous models, yielding an accuracy of 96.15%. So far, BAGGING has been the best method.

preds = predict(rf_model,test_set, type="raw")
preds = predict(rf_model,test_set, type="raw")
preds <- mapvalues(preds, from=c(0,1), to=c(1,2))
confusionMatrix(preds, as.factor(test_set$Classes),
                mode = "everything",
                positive='1')
Confusion Matrix and Statistics

          Reference
Prediction  1  2
         1 22  1
         2  1 28
                                          
               Accuracy : 0.9615          
                 95% CI : (0.8679, 0.9953)
    No Information Rate : 0.5577          
    P-Value [Acc > NIR] : 5.691e-11       
                                          
                  Kappa : 0.922           
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.9565          
            Specificity : 0.9655          
         Pos Pred Value : 0.9565          
         Neg Pred Value : 0.9655          
              Precision : 0.9565          
                 Recall : 0.9565          
                     F1 : 0.9565          
             Prevalence : 0.4423          
         Detection Rate : 0.4231          
   Detection Prevalence : 0.4423          
      Balanced Accuracy : 0.9610          
                                          
       'Positive' Class : 1               
                                          

BOOSTING

In boosting, multiple models are trained sequentially and each model learns from the errors of its predecessors. We will use the Stochastic Gradient Boosting algorithm.

Stochastic Gradient Boosting

Training the model

An important thing to note is that stochastic gradient boosting takes a much longer time to train as it is a step-wise method which takes a lot of iterations to converge. Adding cross-validation makes it even longer.

control <- trainControl(method="cv", number=5)
control <- trainControl(method="cv", number=5)
SGB <- train(Classes ~ Temperature+Rain+FFMC+DMC+DC+ISI+BUI+FWI+RH, data=train_set, method="gbm", metric="Accuracy", trControl=control)
Training accuracy

100% accuracy on training data.

confusionMatrix(preds, train_set$Classes,
                mode = "everything",
                positive='1')
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0  83   0
         1   0 108
                                     
               Accuracy : 1          
                 95% CI : (0.9809, 1)
    No Information Rate : 0.5654     
    P-Value [Acc > NIR] : < 2.2e-16  
                                     
                  Kappa : 1          
                                     
 Mcnemar's Test P-Value : NA         
                                     
            Sensitivity : 1.0000     
            Specificity : 1.0000     
         Pos Pred Value : 1.0000     
         Neg Pred Value : 1.0000     
              Precision : 1.0000     
                 Recall : 1.0000     
                     F1 : 1.0000     
             Prevalence : 0.5654     
         Detection Rate : 0.5654     
   Detection Prevalence : 0.5654     
      Balanced Accuracy : 1.0000     
                                     
       'Positive' Class : 1          
                                     
Testing accuracy

98.08% accuracy on unseen data, same as the training set, boosting brought much improvement over our random forest model even though it took a greater training time.

confusionMatrix(preds, as.factor(test_set$Classes),
                mode = "everything",
                positive='1')
Confusion Matrix and Statistics

          Reference
Prediction  1  2
         1 23  1
         2  0 28
                                          
               Accuracy : 0.9808          
                 95% CI : (0.8974, 0.9995)
    No Information Rate : 0.5577          
    P-Value [Acc > NIR] : 2.743e-12       
                                          
                  Kappa : 0.9612          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 1.0000          
            Specificity : 0.9655          
         Pos Pred Value : 0.9583          
         Neg Pred Value : 1.0000          
              Precision : 0.9583          
                 Recall : 1.0000          
                     F1 : 0.9787          
             Prevalence : 0.4423          
         Detection Rate : 0.4423          
   Detection Prevalence : 0.4615          
      Balanced Accuracy : 0.9828          
                                          
       'Positive' Class : 1               
                                          

Final notes on Ensemble methods

  • We can see that our basic model yielded very good performance on unseen data, but we would like to point out that our dataset is quite small in size, therefore ensemble methods like random forests will not perform very well as they need considerable amounts of data in order to build multiple trees and apply their algorithms properly.
  • That is why for our specific problem there is no need to go to these advanced tree-based methods. However, we could still notice a very good performance from boosting and bagging methods.
  • Another note is that the results are very dependent on the training set, meaning that small fluctuations in train and test accuracy will happen if we change the random seed used in the train-test split for instance. This has happened during our experiments as they resulted in different values on different laptops and for different values of the seed.

SVM

Support Vector Machine is a discriminative classifier that classifies obserations using a hyperplane that best differentiates between the classes. Its advantages lay in the fact that they are very flexible and work well on high-dimensional data.

We will use SVM on our dataset to demonstrate its capabilities.

Training the model

The goal of the SVM is to identify a boundary that minimizes the total distance between the hyper-plane and the closest points on each class.

There are two hyper-parameters to take into consideration before training our SVM model: first, the cost C which acts as a regularization parameter and trades off correct classifications of the training examples against the maximization of the decision boundary. In other words, the greater the value of C the higher the number of errors occurring in the training classifications. The second hyper-parameter gamma defines how much curvature we want in our decision boundary.

We start by tuning our model according to different values of gamma and C. We will start by using a linear kernel.

To use the cross validation functions from the Caret package, we need to turn the 0/1 categorical values of the variable Classes into “fire”/“not_fire” (as required). The functions provided will allow us to find the best values for both gamma and C. we used a tuning length of 10.

train_set_svm$Classes
  [1] not_fire not_fire not_fire not_fire fire     fire     not_fire not_fire
  [9] fire     fire     not_fire not_fire not_fire not_fire not_fire not_fire
 [17] fire     not_fire not_fire fire     fire     fire     fire     not_fire
 [25] fire     fire     fire     fire     fire     fire     fire     fire    
 [33] not_fire fire     fire     fire     fire     fire     fire     fire    
 [41] fire     fire     not_fire not_fire fire     fire     fire     not_fire
 [49] fire     fire     fire     fire     not_fire not_fire fire     fire    
 [57] fire     fire     fire     fire     fire     fire     fire     fire    
 [65] fire     fire     fire     fire     not_fire fire     fire     fire    
 [73] not_fire not_fire fire     not_fire not_fire fire     not_fire not_fire
 [81] fire     fire     fire     fire     fire     fire     fire     not_fire
 [89] fire     not_fire not_fire fire     not_fire not_fire not_fire not_fire
 [97] not_fire not_fire not_fire fire     fire     not_fire not_fire fire    
[105] fire     not_fire not_fire not_fire not_fire not_fire not_fire not_fire
[113] fire     fire     fire     fire     fire     fire     not_fire fire    
[121] not_fire not_fire fire     not_fire fire     not_fire not_fire not_fire
[129] not_fire not_fire not_fire not_fire fire     fire     fire     not_fire
[137] not_fire not_fire fire     fire     fire     fire     fire     not_fire
[145] not_fire fire     fire     fire     fire     not_fire fire     fire    
[153] fire     fire     fire     fire     fire     fire     fire     fire    
[161] fire     fire     fire     fire     fire     fire     not_fire not_fire
[169] not_fire not_fire not_fire fire     not_fire not_fire not_fire not_fire
[177] not_fire not_fire not_fire not_fire fire     fire     fire     fire    
[185] fire     not_fire not_fire not_fire not_fire not_fire not_fire
Levels: not_fire fire

We perform cross validation repeated 5 times.

svm_model_radial
Support Vector Machines with Radial Basis Function Kernel 

191 samples
  9 predictor
  2 classes: 'not_fire', 'fire' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 5 times) 
Summary of sample sizes: 172, 172, 172, 172, 172, 173, ... 
Resampling results across tuning parameters:

  C       ROC        Sens       Spec     
    0.25  0.9879874  0.9294444  0.9383636
    0.50  0.9920732  0.9294444  0.9458182
    1.00  0.9937298  0.9441667  0.9441818
    2.00  0.9930960  0.9516667  0.9552727
    4.00  0.9933434  0.9591667  0.9534545
    8.00  0.9927121  0.9688889  0.9612727
   16.00  0.9935934  0.9663889  0.9687273
   32.00  0.9944798  0.9519444  0.9687273
   64.00  0.9950631  0.9475000  0.9778182
  128.00  0.9947879  0.9280556  0.9760000

Tuning parameter 'sigma' was held constant at a value of 0.1459001
ROC was used to select the optimal model using the largest value.
The final values used for the model were sigma = 0.1459001 and C = 64.

Using the radial kernel resulted in an AUC of 99% while the linear gave us an AUC of 99.8%.

We will then proceed to show the confusion matrix of the model.

confusionMatrix(preds_svm_test_radial,test_set_svm$Classes, positive="fire")
Confusion Matrix and Statistics

          Reference
Prediction not_fire fire
  not_fire       21    1
  fire            2   28
                                          
               Accuracy : 0.9423          
                 95% CI : (0.8405, 0.9879)
    No Information Rate : 0.5577          
    P-Value [Acc > NIR] : 7.729e-10       
                                          
                  Kappa : 0.8825          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.9655          
            Specificity : 0.9130          
         Pos Pred Value : 0.9333          
         Neg Pred Value : 0.9545          
             Prevalence : 0.5577          
         Detection Rate : 0.5385          
   Detection Prevalence : 0.5769          
      Balanced Accuracy : 0.9393          
                                          
       'Positive' Class : fire            
                                          

For the radial kernel: our model gave us an accuracy of 100% on the training set and 94.23% on the test set.

For the linear kernel: the model gave an accuracy of 98.95% on the train set and performed worse than the radial kernel, which is expected since it is less flexible and fits the data less well. On the test set however, it gave us an accuracy of 96.15%.

Overall, the linear kernel did a better job at generalizing on unseen data, which is why we will go for it in our model.

Plotting the ROC curve

The model with the linear kernel gave us an AUC of 96.10%

auc 
[1] 0.9610195
LS0tCnRpdGxlOiA8Y2VudGVyPkFsZ2VyaWFuIEZvcmVzdCBGaXJlIEFuYWx5c2lzPC9jZW50ZXI+PGJyLz4Kb3V0cHV0OiBodG1sX25vdGVib29rCmF1dGhvcjoKICAtIE1vaGFtZWQgUmlzc2FsIEhlZG5hIDIwMTkwNjIzMwogIC0gWW91bmVzIERqZW1tYWwgMjAxOTA2MzAzCi0tLQojIyBPdmVydmlldwojIyMgQmFja2dyb3VuZAoKRHVyaW5nIHRoZSBzdW1tZXIgb2YgMjAxMiwgW3dpbGQgZmlyZXNdKGh0dHBzOi8vd3d3LnVuLXNwaWRlci5vcmcvbmV3cy1hbmQtZXZlbnRzL25ld3MvYWxnZXJpYS1tYXBzLXN1bW1lci0yMDEyLXdpbGRmaXJlcy1hdmFpbGFibGUpIHJhdmFnZWQgdGhyb3VnaG91dCB0aGUgQWxnZXJpYW4gdGVycml0b3J5IGNvdmVyaW5nIG1vc3Qgb2YgdGhlIG5vcnRoZXJuIHBhcnQsIGVzcGVjaWFsbHkgdGhlIGNvYXN0YWwgY2l0aWVzLiBUaGlzIGRpc2FzdGVyIHdhcyBkdWUgdG8gdGhlIGhpZ2hlciB0aGFuIGF2ZXJhZ2UgdGVtcGVyYXR1cmVzIHdoaWNoIHJlYWNoZWQgYXMgaGlnaCBhcyA1MCBkZWdyZWVzIENlbGNpdXMuCgojIyMgT2JqZWN0aXZlcyAKCk9uZSBpbXBvcnRhbnQgbWVhc3VyZSBhZ2FpbnN0IHRoZSByZXByb2R1Y3Rpb24gb2Ygc3VjaCBkaXNhc3RlcnMgaXMgdGhlIGFiaWxpdHkgdG8gcHJlZGljdCB0aGVpciBvY2N1cnJlbmNlLiBNb3Jlb3ZlciwgaW4gdGhpcyBwcm9qZWN0LCB3ZSB3aWxsIGF0dGVtcHQgdG8gcHJlZGljdCB0aGVzZSBmb3Jlc3QgZmlyZXMgYmFzZWQgb24gbXVsdGlwbGUgZmVhdHVyZXMgcmVsYXRlZCB0byB3ZWF0aGVyIGluZGljZXMuCgojIyMgRGF0YXNldCBEZXNjcmlwdGlvbgoKVGhlIERhdGFzZXQgd2Ugd2lsbCB1c2UgdG8gdHJhaW4gYW5kIHRlc3Qgb3VyIG1vZGVscyBjb25zaXN0cyBvZiAyNDQgb2JzZXJ2YXRpb25zIG9uIHR3byBBbGdlcmlhbiBXaWxheWFzIChjaXRpZXMpOiBTaWRpLUJlbCBBYmJlcyBhbmQgQmVqYWlhLiBUaGUgb2JzZXJ2YXRpb25zIGhhdmUgYmVlbiBnYXRoZXJlZCB0aHJvdWdob3V0IHRoZSBkdXJhdGlvbiBvZiA0IG1vbnRocyBmcm9tIEp1bmUgdG8gU2VwdGVtYmVyIDIwMTIgZm9yIGJvdGggY2l0aWVzLgoKKipUaGUgRGF0YXNldCBjb250YWlucyB0aGUgZm9sbG93aW5nIHZhcmlhYmxlczoqKgoKMS4gRGF0ZTogKEREL01NL1lZWVkpIERheSwgbW9udGggKCdqdW5lJyB0byAnc2VwdGVtYmVyJyksIHllYXIgKDIwMTIpCjIuIFRlbXA6IHRlbXBlcmF0dXJlIG5vb24gKHRlbXBlcmF0dXJlIG1heCkgaW4gQ2Vsc2l1cyBkZWdyZWVzOiAyMiB0byA0MgozLiBSSDogUmVsYXRpdmUgSHVtaWRpdHkgaW4gJTogMjEgdG8gOTAKNC4gV3M6IFdpbmQgc3BlZWQgaW4ga20vaDogNiB0byAyOQo1LiBSYWluOiB0b3RhbCBkYXkgaW4gbW06IDAgdG8gMTYuODxicj4KKipGV0kgQ29tcG9uZW50cyAoY2hlY2sgdGhpcyBbTElOS10oaHR0cHM6Ly9jd2Zpcy5jZnMubnJjYW4uZ2MuY2EvYmFja2dyb3VuZC9zdW1tYXJ5L2Z3aSkgZm9yIG1vcmUgaW5mb3JtYXRpb24pKioKNi4gRmluZSBGdWVsIE1vaXN0dXJlIENvZGUgKEZGTUMpIGluZGV4IGZyb20gdGhlIEZXSSBzeXN0ZW06IDI4LjYgdG8gOTIuNQo3LiBEdWZmIE1vaXN0dXJlIENvZGUgKERNQykgaW5kZXggZnJvbSB0aGUgRldJIHN5c3RlbTogMS4xIHRvIDY1LjkKOC4gRHJvdWdodCBDb2RlIChEQykgaW5kZXggZnJvbSB0aGUgRldJIHN5c3RlbTogNyB0byAyMjAuNAo5LiBJbml0aWFsIFNwcmVhZCBJbmRleCAoSVNJKSBpbmRleCBmcm9tIHRoZSBGV0kgc3lzdGVtOiAwIHRvIDE4LjUKMTAuIEJ1aWxkLXVwIEluZGV4IChCVUkpIGluZGV4IGZyb20gdGhlIEZXSSBzeXN0ZW06IDEuMSB0byA2OAoxMS4gRmlyZSBXZWF0aGVyIEluZGV4IChGV0kpIEluZGV4OiAwIHRvIDMxLjEKMTIuIENsYXNzZXM6IHR3byBjbGFzc2VzLCBuYW1lbHkgImZpcmUiIGFuZCAibm90IGZpcmUiCgojIyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzCgpXZSBmaXJzdCBzdGFydCBvZmYgYnkgaW1wb3J0aW5nIHRoZSBuZWNlc3NhcnkgbGlicmFyaWVzIGZvciBvdXIgYW5hbHlzaXMuCgpUaGUgbGlicmFyaWVzIHdlIHVzZWQgYXJlIHRoZSBmb2xsb3dpbmc6CgoxLiBlMTA3MTogdGhpcyBwYWNrYWdlIGlzIHVzZWQgdG8gcGVyZm9ybSBzdGF0aXN0aWMgYW5kIHByb2JhYmlsaXN0aWMgYWxnb3JpdGhtcy4gSW4gb3VyIGNhc2UgaXQgaXMgdXNlZCB0byBwZXJmb3JtIFNWTQoyLiBNQVNTOiBUaGlzIHBhY2thZ2UgaW5jbHVkZXMgbWFueSB1c2VmdWwgZnVuY3Rpb25zIGFuZCBkYXRhIGV4YW1wbGVzLCBpbmNsdWRpbmcgZnVuY3Rpb25zIGZvciBlc3RpbWF0aW5nIGxpbmVhciBtb2RlbHMgdGhyb3VnaCBnZW5lcmFsaXplZCBsZWFzdCBzcXVhcmVzIChHTFMpCjMuIHBseXI6IGNvbnRhaW5zIHRvb2xzIGZvciBzcGxpdHRpbmcsIGFwcGx5aW5nIGFuZCBjb21iaW5pbmcgZGF0YQo0LiBjYXJldDogYSBwb3dyZnVsIGxpYnJhcnkgdGhhdCBoYXMgYSB0cmFpbiBmdW5jdGlvbiB3aGljaCBhbGxvd3MgdXMgdG8gZml0IG92ZXIgMjMwIG1vZGVscyB1bmNsdWRpbmcgdHJlZS1iYXNlZCBtb2RlbHMKNS4gUk9DUjogYSBmbGV4aWJsZSB0b29sIGZvciBjcmVhdGluZyBjdXRvZmYtcGFyYW1ldGVyaXplZCAyRCBwZXJmb3JtYW5jZSBjdXJ2ZXMgYnkgZnJlZWx5IGNvbWJpbmluZyB0d28gZnJvbSBvdmVyIDI1IHBlcmZvcm1hbmNlIG1lYXN1cmVzCjYuIHBST0M6IGEgcGFja2FnZSBzcGVjaWZpY2FsbHkgZGVkaWNhdGVkIHRvIFJPQyBhbmFseXNpcwo3LiByYW5kb21Gb3Jlc3Q6IHBlcmZvcm1zIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIG9uIGEgZm9yZXN0IG9mIHRyZWVzIHVzaW5nIHJhbmRvbSBpbnB1dHMKOC4gZ2JtOiBzaG9ydCBmb3IgZ2VuZXJhbGl6ZWQgYm9vc3RlZCBtb2RlbHMsIHByb3ZpZGVzIGV4dGVuc2lvbnMgdG8gU2NoYXBpcmUncyBBZGFCb29zdCBhbGdvcml0aG0KOS4gZHBseXI6IHVzZWQgZm9yIGRhdGEgbWFuaXB1bGF0aW9uIHByb3ZpZGluZyBhIHNldCBvZiBmdW5jdGlvbnMgdGhhdCBhcmUgdmVyeSB1c2VmdWwKMTAuIHRpZHl2ZXJzZTogY29udGFpbnMgbXVsdGlwbGUgZXNzZW5zaWFsIHRvb2xzIGFuZCBwYWNrYWdlcyBzdWNoIGFzIGdncGxvdDIgZm9yIHZpc3VhbGl6YXRpb24KMTEuIGNhVG9vbHM6IHVzZWQgZm9yIHNwbGl0dGluZyBvdXIgZGF0YXNldCBpbnRvIHRyYWluL3Rlc3Qgc2V0cwoKCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KGUxMDcxKQpsaWJyYXJ5KE1BU1MpCmxpYnJhcnkoZHBseXIpCiNsaWJyYXJ5KHZ0YWJsZSkKbGlicmFyeShwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2djb3JycGxvdCkKbGlicmFyeShwbG90bHkpCmxpYnJhcnkodGlkeXZlcnNlKQojRmVhdHVyZSBzZWxlY3Rpb24gbGlicmFyaWVzCiNsaWJyYXJ5KG1sYmVuY2gpCmxpYnJhcnkoY2FyZXQpCiNGb3IgTG9naXN0aWMgcmVncmVzc2lvbgpsaWJyYXJ5KGNhVG9vbHMpCiNGb3IgUk9DIGN1cnZlCmxpYnJhcnkoUk9DUikKbGlicmFyeShwUk9DKQojZW5zZW1sZSBtZXRob2RzCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KGdibSkKYGBgCgoKIyMjIEltcG9ydGluZyB0aGUgZGF0YSAKClRoZSBEYXRhc2V0IHByb3ZpZGVkIHRvIHVzIHdhcyBpbiB0aGUgZm9ybSBvZiBhIC5jc3YgZmlsZSB0aGF0IGNvbnRhaW5lZCB0d28gdGFibGVzLCBvbmUgdGFibGUgZm9yIHRoZSBvYnNlcnZhdGlvbnMgYmVsb25naW5nIHRvIHRoZSBTaWRpLUJlbCBBYmJlcyByZWdpb24sIGFuZCB0aGUgb3RoZXIgZm9yIEJlamFpYS4gCgpCZWZvcmUgc3RhcnRpbmcgb3VyIGFuYWx5c2lzIHdlIHNlcGFyYXRlZCB0aGUgdGFibGVzIGludG8gdHdvIGRpc3RpbmN0IGZpbGVzIGFjY29yZGluZyB0byB0aGUgcmVnaW9uLiBXZSBuYW1lZCBib3RoIGZpbGVzICpBbGdlcmlhbl9mb3Jlc3RfZmlyZXNfZGF0YXNldF9CZWphaWEuY3N2KiBhbmQgKkFsZ2VyaWFuX2ZvcmVzdF9maXJlc19kYXRhc2V0X1NpZGlfQmVsX0FiYmVzLmNzdiogZm9yIEJlamFpYSBhbmQgU2lkaS1CZWwgQWJiZXMgcmVzcGVjdGl2ZWx5LgoKCmBgYHtyLCBlY2hvPUZBTFNFfQpkZl9iIDwtIHJlYWQuY3N2KCIuL0FsZ2VyaWFuX2ZvcmVzdF9maXJlc19kYXRhc2V0X0JlamFpYS5jc3YiKQpkZl9zIDwtIHJlYWQuY3N2KCIuL0FsZ2VyaWFuX2ZvcmVzdF9maXJlc19kYXRhc2V0X1NpZGlfQmVsX0FiYmVzLmNzdiIpCmBgYAoKIyMjIENsZWFuaW5nIGFuZCBwcm9jZXNzaW5nIHRoZSBkYXRhCgpXZSBmaXJzdCBjaGVjayB0aGUgZXhpc3RlbmNlIG9mIG51bGwgdmFsdWVzIGluIHRoZSBEYXRhc2V0LCBub25lIHdlcmUgZm91bmQuCgoKYGBge3J9CmNvbFN1bXMoaXMubmEoZGZfYikpCmNvbFN1bXMoaXMubmEoZGZfcykpCmBgYAoKV2UgdGhlbiBwcm9jZXNzIHRvIGFkZCBhIGNvbHVtbiBpbiBib3RoIGRhdGFzZXRzIHRvIGluZGljYXRlIHRoZSByZWdpb24oV2lsYXlhKSBpbiBlYWNoIHRhYmxlLiBXZSBjaG9zZSB0aGUgZm9sbG93aW5nIGVuY29kaW5nOgoKMS4gQmVqYWlhID0gMAoyLiBTaWRpLUJlbCBBYmJlcyA9IDEKCgpgYGB7cn0KZGZfYltbIlJlZ2lvbiJdXSA9IDAKZGZfc1tbIlJlZ2lvbiJdXSA9IDEKYGBgCgpBZnRlciB0aGF0LCB3ZSBwcm9jZWVkIHRvIG1lcmdlIGJvdGggb3VyIGRhdGFzZXRzIGludG8gb25lIHNpbmdsZSBkYXRhZnJhbWUgdXNpbmcgKmZ1bGxfam9pbigpKiwgdGhpcyB3aWxsIGFsbG93IHVzIHRvIGVhc2lseSBleHBsb3JlIGFuZCBhbmFseXplIHRoZSBkYXRhLgoKYGBge3J9CmRmX3MkREMgPC0gYXMuZG91YmxlKGRmX3MkREMpCmRmX3MkRldJIDwtIGFzLmRvdWJsZShkZl9zJEZXSSkKCmRmID0gZnVsbF9qb2luKGRmX3MsIGRmX2IpCgpkaW0oZGYpCnN0cihkZikKYGBgCgpgYGB7cn0Kc3VtbWFyeShkZikKdW5pcXVlKGRmJHllYXIpCnVuaXF1ZShkZiRtb250aCkKYGBgCgpXZSBjaGVjayBhZ2FpbiBmb3IgYW55ICpOQSogdmFsdWVzIHRoYXQgbWlnaHQgaGF2ZSBiZWVuIGludHJvZHVjZWQgaW50byB0aGUgZGF0YXNldCBieSBtZXJnaW5nIHRoZSBkYXRhIGZyb20gYm90aCB0YWJsZXMsIHdlIGZvdW5kIG91dCB0aGVyZSB3YXMgb25lIHJvdyB0aGF0IGNvbnRhaW5lZCBOQSB2YWx1ZSBpbiBEQyBhbmQgRldJLiBXZSBkZWxldGUgdGhhdCByb3cgc2luY2UgaXQgd2lsbCBub3QgYWZmZWN0IG91ciBvdmVyYWxsIGRhdGFzZXQuCgpgYGB7cn0KY29sU3Vtcyhpcy5uYShkZikpCmRmID0gZGYgJT4lIGRyb3BfbmEoREMpCmRpbShkZikKYGBgCgpXZSBub3cgcHJvY2VlZCB0byBkaXNwbGF5IHRoZSBkaWZmZXJlbnQgcmFuZ2Ugb2YgdmFsdWVzIHNvbWUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIG1pZ2h0IGNvbnRhaW4sIG1haW5seSB0aGUgQ2xhc3NlcyBhbmQgdGhlIFJlZ2lvbiBjb2x1bW5zLgoKCmBgYHtyfQp1bmlxdWUoZGYkQ2xhc3NlcykKdW5pcXVlKGRmJFJlZ2lvbikKYGBgCgpXZSBmaW5kIHRoYXQgdGhlIENsYXNzZXMgY29sdW1uIGhhcyB2YWx1ZXMgdGhhdCBjb250YWluIHVubmVlZGVkIHNwYWNlIGNoYXJhY3RlcnMsIHdlIHByb2NlZWQgdG8gdHJpbSB0aG9zZSBzcGFjZXMuCgpgYGB7cn0KZGYkQ2xhc3NlcyA8LSB0cmltd3MoZGYkQ2xhc3Nlcywgd2hpY2ggPSBjKCJib3RoIikpCmBgYAoKCgpgYGB7cn0KdW5pcXVlKGRmJENsYXNzZXMpCmRmID0gZGYgJT4lIGRyb3BfbmEoQ2xhc3NlcykKYGBgCgpXZSB0aGVuIHR1cm4gdGhlIGZpcmUvbm90IGZpcmUgdmFsdWVzIGludG8gMS8wIHJlc3BlY3RpdmVseSBmb3IgZnV0dXJlIGFuYWx5c2lzLgoKYGBge3J9CmRmJENsYXNzZXMgPC0gbWFwdmFsdWVzKGRmJENsYXNzZXMsIGZyb209Yygibm90IGZpcmUiLCJmaXJlIiksIHRvPWMoMCwxKSkKYGBgCgpgYGB7cn0KdW5pcXVlKGRmJENsYXNzZXMpCmRmJENsYXNzZXMgPC0gYXMubnVtZXJpYyhkZiRDbGFzc2VzKQpzdHIoZGYpCmBgYAoKV2UgZGVsZXRlIHRoZSB5ZWFyIGNvbHVtbiBzaW5jZSBhbGwgb2JzZXJ2YXRpb25zIHdlcmUgcGVyZm9ybWVkIGluIHRoZSBzYW1lIHllYXIKCmBgYHtyfQpkZiA8LSBkZlstYygzKV0KCmRmX3NjYWxlZCA9IGRmCmRmX3NjYWxlZFstYygxLDIsMTMsMTQpXSA8LSBzY2FsZShkZlstYygxLDIsMTMsMTQpXSkKc3RyKGRmX3NjYWxlZCkKCmBgYAoKIyMjIFZpc3VhbGl6aW5nIHRoZSBkYXRhCgpXZSBoYXZlIGVuZGVkIHVwIHdpdGggYSBjbGVhbiBhbmQgc2NhbGVkIGRhdGFmcmFtZSBuYW1lZCAqZGZfc2NhbGVkKiwgd2hpY2ggd2Ugd2lsbCB1c2UgdG8gdmlzdWFsaXplIGFuZCBmdXJ0aGVyIGV4cGxvcmUgb3VyIGRhdGEuCgpPdXIgZmlyc3QgaW5zdGluY3QgaXMgdG8gY29tcGFyZSB0aGUgdHdvIHJlZ2lvbnMgdG9nZXRoZXIgaW4gdGVybXMgb2YgbnVtYmVyIG9mIGZpcmVzLCBhbmQgYXZlcmFnZSB0ZW1wZXJhdHVyZS4gCgpgYGB7cn0KYWdncmVnYXRlKGRmJENsYXNzZXMgfiBkZiRSZWdpb24sIEZVTiA9IHN1bSkKYWdncmVnYXRlKGRmJFRlbXBlcmF0dXJlIH4gZGYkUmVnaW9uLCBGVU4gPSBtZWFuKQpgYGAKV2UgdXNlZCB0aGUgdW5zY2FsZWQgZGF0YXNldCB0byBwbG90IHRoZSByZWFsIGxpZmUgdmFsdWVzIG9mIHRoZSB0ZW1wZXJhdHVyZXMuCgpgYGB7cn0KZGYgJT4lCiAgZ3JvdXBfYnkoUmVnaW9uKSAlPiUKICBzdW1tYXJpc2UoUmVnaW9uID0gUmVnaW9uLCBOdW1iZXJfb2ZfZmlyZXMgPSBzdW0oQ2xhc3NlcyksIFRlbXBlcmF0dXJlID0gbWVhbihUZW1wZXJhdHVyZSkpICU+JQogIGdncGxvdChhZXMoeD1SZWdpb24sIHk9TnVtYmVyX29mX2ZpcmVzLCBmaWxsID0gVGVtcGVyYXR1cmUpKSsKICBnZW9tX2NvbChwb3NpdGlvbj0nZG9kZ2UnKQpgYGAKCldlIGNhbiBzZWUgdGhhdCB0aGUgdGhlIFNpZGktQmVsIEFiYmVzIHJlZ2lvbiBoYXMgaW4gdG90YWwgYSBncmVhdGVyIG51bWJlciBvZiBmaXJlcyBhbmQgYSBoaWdoZXIgYXZlcmFnZSB0ZW1wZXJhdHVyZSB0aHJvdWdob3V0IHRoZSBzdW1tZXIgb2YgMjAxMi4KCiMjIEZ1cnRoZXIgQW5hbHlzaXMKIyMjIENvcnJlbGF0aW9uIE1hdHJpeAoKVGhlIHByZXZpb3VzIHJlc3VsdHMgcHVzaCB1cyB0byBzdXNwZWN0IGEgcG9zaXRpdmUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHRlbXBlcmF0dXJlIGFuZCB0aGUgbGlrZWxpaG9vZCBvZiBoYXZpbmcgYSBmaXJlLiBIb3dldmVyLCB3ZSBuZWVkIHRvIGludmVzdGlnYXRlIGFsbCB0aGUgb3RoZXIgdmFyaWFibGVzLCB3aGljaCBpcyB3aHkgd2Ugd2lsbCBwbG90IGEgY29ycmVsYXRpb24gbWF0cml4IG9mIHRoZSBmZWF0dXJlcyBpbiB0aGUgZGF0YXNldC4KCmBgYHtyfQpjb3JyX21hdCA8LSByb3VuZChjb3IoZGZfc2NhbGVkKSwyKQpwX21hdCA8LSBjb3JfcG1hdChkZl9zY2FsZWQpCiAKY29ycl9tYXQgPC0gZ2djb3JycGxvdCgKICBjb3JyX21hdCwgCiAgaGMub3JkZXIgPSBGQUxTRSwgCiAgdHlwZSA9ICJ1cHBlciIsCiAgb3V0bGluZS5jb2wgPSAid2hpdGUiLAopCiAKZ2dwbG90bHkoY29ycl9tYXQpCmBgYAoKIyMjIEZlYXR1cmUgU2VsZWN0aW9uCgpXZSBwZXJmb3JtZWQgZmVhdHVyZSBzZWxlY3Rpb24gdXNpbmcgdGhlIENhcmV0IHBhY2thZ2UgdG8gZGV0ZXJtaW5lIHdoaWNoIGZlYXR1cmVzIGFyZSB0aGUgbW9zdCBpbXBvcnRhbnQgYW5kIHdoaWNoIGFyZSB0aGUgbGVhc3QuIAoKSW4gdGhpcyBjYXNlLCB3ZSBvcHRlZCBmb3IgTGluZWFyIERpc2NyaW1pbmFudCBBbmFseXNpcyB3aXRoIFN0ZXB3aXNlIEZlYXR1cmUgU2VsZWN0aW9uIGJ5IHNwZWNpZnlpbmcgKnN0ZXBMREEqIGFzIG91ciBtZXRob2QuCgpUaGUgKnZhckltcCogZnVuY3Rpb24gcmV0dXJucyBhIG1lYXN1cmUgb2YgaW1wb3J0YW5jZSBvdXQgb2YgMTAwIGZvciBlYWNoIG9mIHRoZSBmZWF0dXJlcy4gQWNjb3JkaW5nIHRvIHRoZSBvZmZpY2lhbCBbQ2FyZXQgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly90b3BlcG8uZ2l0aHViLmlvL2NhcmV0L3ZhcmlhYmxlLWltcG9ydGFuY2UuaHRtbCksIHRoZSBpbXBvcnRhbmNlIG1ldHJpYyBpcyBjYWxjdWxhdGVkIGJ5IGNvbmR1Y3RpbmcgYSBST0MgY3VydmUgYW5hbHlzaXMgb24gZWFjaCBwcmVkaWN0b3I7IGEgc2VyaWVzIG9mIGN1dG9mZnMgaXMgYXBwbGllZCB0byB0aGUgcHJlZGljdG9yIGRhdGEgdG8gcHJlZGljdCB0aGUgY2xhc3MuIFRoZSBBVUMgaXMgdGhlbiBjb21wdXRlZCBhbmQgaXMgdXNlZCBhcyBhIG1lYXN1cmUgb2YgdmFyaWFibGUgaW1wb3J0YW5jZS4gCgoKYGBge3J9CiMgcHJlcGFyZSB0cmFpbmluZyBzY2hlbWUKZGZfc2NhbGVkJENsYXNzZXMgPSBhcy5mYWN0b3IoZGZfc2NhbGVkJENsYXNzZXMpCgpjb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2Q9InJlcGVhdGVkY3YiLCBudW1iZXI9MTAsIHJlcGVhdHM9MykKIyB0cmFpbiB0aGUgbW9kZWwKbW9kZWxMREEgPC0gdHJhaW4oQ2xhc3Nlc34uLCBkYXRhPWRmX3NjYWxlZCwgbWV0aG9kPSJzdGVwTERBIiwgdHJDb250cm9sPWNvbnRyb2wpCgppbXBvcnRhbmNlTERBIDwtIHZhckltcChtb2RlbExEQSwgc2NhbGU9RkFMU0UpCgpwbG90KGltcG9ydGFuY2VMREEpCmBgYApXZSBjYW4gc2VlIHRoYXQgdGhlIHZhcmlhYmxlcyAqbW9udGgqLCAqV3MqLCAqUmVnaW9uKiwgYW5kICpkYXkqIGFyZSBpbnNpZ25pZmljYW50IGNvbXBhcmVkIHRvIG90aGVyIGZlYXR1cmVzLiBXZSB3aWxsIGRpc3JlZ2FyZCB0aGVtIGluIG91ciBtb2RlbC4gVG8gZGV0ZXJtaW5lIHRoaXMgd2UgdXNlZCBhIHRocmVzaG9sZCBvZiAwLjcgZm9yIHRoZSAqaW1wb3J0YW5jZSogbWVhc3VyZS4KCgoKIyMgTW9kZWwgQnVpbGRpbmcKCkZvciB0aGUgZm9sbG93aW5nIG1vZGVscywgd2Ugd2lsbCBvbmx5IHVzZSB0aGUgZmVhdHVyZXMgdGhhdCB3ZXJlIHRoZSBtb3N0IHNpZ25pZmljYW50IGluIG91ciBmZWF0dXJlIHNlbGVjdGlvbiBwaGFzZS4gVGhlIHNlbGVjdGVkIGZlYXR1cmVzIGFyZToKCjEuIFRlbXBlcmF0dXJlCjIuIFJhaW4KMy4gRkZNQwo0LiBETUMKNS4gREMKNi4gSVNJCjcuIEJVSQo4LiBGV0kKOS4gUkgKCiMjIyBTcGxpdHRpbmcgdGhlIGRhdGFzZXQKCldlIGJlZ2luIGJ5IHNwbGl0dGluZyB0aGUgZGF0YSBpbnRvIHRyYWluL3Rlc3Qgc2V0cyB3aXRoIGEgODAvMjAgc3BsaXQuIFRoaXMgc3BsaXQgd2FzIGNob3NlbiBieSBkZWZhdWx0IGFzIGEgZ29vZCBwcmFjdGljZS4gVGhpcyB3aWxsIGxlYXZlIHVzIHdpdGggMTkxIG9ic2VydmF0aW9ucyBpbiB0aGUgdHJhaW5pbmcgc2V0IGFzIHdlbGwgYXMgNTIgaW4gdGhlIHRlc3Qgc2V0LiBEdWUgdG8gdGhlIHNtYWxsIG5hdHVyZSBvZiB0aGUgZGF0YXNldCBhdCBoYW5kIHdlIHdpbGwgbGF0ZXIgYXBwbHkgY3Jvc3MgdmFsaWRhdGlvbiB0byBzb21lIG1vZGVscyBpbiBvcmRlciB0byBmdXJ0aGVyIGV4YW1pbmUgdGhlaXIgcGVyZm9ybWFuY2UgYW5kIGNvbXBhcmUgdGhlbSB3aXRoIGVhY2ggb3RoZXIuCgpXZSBzZXQgYSBzZWVkIG9mIDEwMDAKCgpgYGB7cn0Kc2V0LnNlZWQoMTAwMCkKc3BsaXQgPC0gc2FtcGxlLnNwbGl0KGRmX3NjYWxlZCwgU3BsaXRSYXRpbz0wLjgpCgp0cmFpbl9zZXQgPC0gc3Vic2V0KGRmX3NjYWxlZCwgc3BsaXQgPT0gIlRSVUUiKQp0ZXN0X3NldCA8LSBzdWJzZXQoZGZfc2NhbGVkLCBzcGxpdD09IkZBTFNFIikKCmRpbSh0cmFpbl9zZXQpCmRpbSh0ZXN0X3NldCkKCmBgYAoKIyMjIExvZ2lzdGljIFJlZ3Jlc3Npb24KCkxvZ2lzdGljIFJlZ3Jlc3Npb24gaXMgY29uc2lkZXJlZCB0byBiZSBhbiBleHRlbnNpb24gb2YgTGluZWFyIFJlZ3Jlc3Npb24sIGluIHdoaWNoIHdlIHByZWRpY3QgdGhlIHF1YWxpdGF0aXZlIHJlc3BvbnNlIGZvciBhbiBvYnNlcnZhdGlvbi4gSXQgZ2l2ZXMgdXMgdGhlIHByb2JhYmlsaXR5IG9mIGEgY2VydGFpbiBvYnNlcnZhdGlvbiBiZWxvbmdpbmcgdG8gYSBjbGFzcyBpbiBiaW5vbWlhbCBjbGFzc2lmaWNhdGlvbiwgYnV0IGNhbiBhbHNvIGJlIGV4dGVuZGVkIHRvIGJlIHVzZWQgZm9yIG11bHRpcGxlIGNsYXNzaWZpY2F0aW9ucy4KCgojIyMjIFRyYWluaW5nIHRoZSBtb2RlbAoKV2UgZmlyc3Qgc3RhcnQgYnkgZml0dGluZyBvdXIgbW9kZWwgb24gdGhlIHRyYWluaW5nIHNldC4gQXMgd2UgZG8gdGhhdCB3ZSBnZXQgYW4gZXJyb3IgdGhhdCBvdXIgbW9kZWwgZGlkIG5vdCBjb252ZXJnZSwgdGhpcyBpcyBkdWUgdG8gb3VyIG1vZGVsIGJlaW5nIGFibGUgdG8gcGVyZmVjdGx5IHNwbGl0IHRoZSBkYXRhc2V0IGludG8gcG9zaXRpdmUvbmVnYXRpdmUgb2JzZXJ2YXRpb25zLiBUaGlzIG1pZ2h0IHNvdWQgY291bnRlcmludHVpdGl2ZSBidXQgdGhpcyBlcnJvciBpcyBhIGdvb2Qgc2lnbi4KCmBgYHtyfQpsb2dpc3RpY19tb2RlbCA8LSBnbG0oQ2xhc3NlcyB+IFRlbXBlcmF0dXJlK1JhaW4rRkZNQytETUMrREMrSVNJK0JVSStGV0krUkgsIGRhdGE9dHJhaW5fc2V0LCBmYW1pbHk9ImJpbm9taWFsIikKCmxvZ2lzdGljX21vZGVsCmBgYAoKIyMjIyBUZXN0aW5nIHRoZSBtb2RlbAoKU2luY2UgbG9naXN0aWMgcmVncmVzc2lvbiBnaXZlcyB1cyB0aGUgcHJvYmFiaWxpdHkgb2YgZWFjaCBvYnNlcnZhdGlvbiBiZWxvbmdpbmcgdG8gdGhlIDEgY2xhc3MsIHdlIHdpbGwgdXNlIGEgMC41IHRocmVzaG9sZCB0byB0cmFuc2Zvcm0gdGhhdCBwcm9iYWJpbGl0eSBpbnRvIGEgY2xhc3NpZmljYXRpb24gb2YgZWl0aGVyIDAgb3IgMS4KCkFmdGVyIGdldHRpbmcgb3VyIHByZWRpY3Rpb25zLCB3ZSB3aWxsIHVzZSB0aGUgY29uZnVzaW9uIG1hdHJpeCBmdW5jdGlvbiBmcm9tIHRoZSBjYXJldCBsaWJyYXJ5IHRoYXQgY29tcHV0ZXMgYSBzZXQgb2YgcGVyZm9ybWFuY2UgbWF0cmljZXMgaW5jbHVkaW5nIGYxLXNjb3JlLCByZWNhbGwgYW5kIHByZWNpc2lvbi4gT3RoZXIgbWF0cmljZXMgY29tcHV0ZWQgaW5jbHVkZTogc2Vuc2l0aXZpdHksIHNwZWNpZmljaXR5LCBwcmV2YWxlbmNlIGV0Yy4gVGhlIG9mZmljaWFsIGRvY3VtZW50YXRpb24gZm9yIHRoaXMgZnVuY3Rpb24gYW5kIHRoZSBmb3JtdWxhcyBmb3IgYWxsIG1hdHJpY2VzIGFyZSBmb3VuZCBpbiB0aGlzIFtsaW5rLl0oaHR0cHM6Ly9yZHJyLmlvL2NyYW4vY2FyZXQvbWFuL2NvbmZ1c2lvbk1hdHJpeC5odG1sKSBXZSB3aWxsIG9ubHkgYmUgaW50ZXJlc3RlZCBpbiB0aGUgZjEtc2NvcmUsIHJlY2FsbCwgcHJlY2lzaW9uLCBhY2N1cmFjeSBhbmQgYmFsYW5jZWQgYWNjdXJhY3kuCgojIyMjIE9uIHRoZSB0cmFpbiBzZXQKCk91ciBtb2RlbCBnaXZlcyB1cyBhbiBhY2N1cmFjeSBhbmQgYW4gZjEgc2NvcmUgb2YgMTAwJSBvbiB0aGUgdHJhaW5pbmcgc2V0LgoKYGBge3J9CnByZWRzX2xvZ2lzdGljIDwtIHByZWRpY3QobG9naXN0aWNfbW9kZWwsIHRyYWluX3NldCwgdHlwZT0icmVzcG9uc2UiKQoKcHJlZHNfbG9naXN0aWMgPC0gaWZlbHNlKHByZWRzX2xvZ2lzdGljID4wLjUsMSwwKQpwcmVkc19sb2dpc3RpYyA8LSBhcy5mYWN0b3IocHJlZHNfbG9naXN0aWMpCgpjb25mdXNpb25NYXRyaXgocHJlZHNfbG9naXN0aWMsIHRyYWluX3NldCRDbGFzc2VzLAogICAgICAgICAgICAgICAgbW9kZSA9ICJldmVyeXRoaW5nIiwKICAgICAgICAgICAgICAgIHBvc2l0aXZlPSIxIikKYGBgCgoKIyMjIyBPbiB0aGUgdGVzdCBzZXQKCk9uIHRoZSB0ZXN0IHNldCBob3Zld2VyLCB3ZSBnZXQgYW4gYWNjdXJhY3kgb2YgOTguMDglIGFuZCBhbiBmMSBzY29yZSBvZiA5OC4yNSUuCgpgYGB7cn0KCnRyYWluX3NldCRDbGFzc2VzIDwtIGFzLmZhY3Rvcih0cmFpbl9zZXQkQ2xhc3NlcykKdGVzdF9zZXQkQ2xhc3NlcyA8LSBhcy5mYWN0b3IodGVzdF9zZXQkQ2xhc3NlcykKCnByZWRzX2xvZ2lzdGljIDwtIHByZWRpY3QobG9naXN0aWNfbW9kZWwsIHRlc3Rfc2V0LCB0eXBlPSJyZXNwb25zZSIpCgpwcmVkc19sb2dpc3RpYyA8LSBpZmVsc2UocHJlZHNfbG9naXN0aWMgPjAuNSwxLDApCnByZWRzX2xvZ2lzdGljIDwtIGFzLmZhY3RvcihwcmVkc19sb2dpc3RpYykKCmNvbmZ1c2lvbk1hdHJpeChwcmVkc19sb2dpc3RpYywgdGVzdF9zZXQkQ2xhc3NlcywKICAgICAgICAgICAgICAgIG1vZGUgPSAiZXZlcnl0aGluZyIsCiAgICAgICAgICAgICAgICBwb3NpdGl2ZT0iMSIpCmBgYAoKIyMjIyBQbG90dGluZyB0aGUgUk9DIGN1cnZlCgpBcyB3ZSBwbG90IHRoZSBST0MgY3VydmUsIHdlIGNhbiBzZWUgdGhhdCB0aGUgQVVDIGlzIGVxdWFsIHRvIDk4LjI3NTg2JSB3aGljaCBpcyBhbG1vc3QgYSBwZXJmZWN0IGNsYXNzaWZpZXIuCgpgYGB7cn0KUk9DUHJlZCA8LSBwcmVkaWN0aW9uKGFzLm51bWVyaWMocHJlZHNfbG9naXN0aWMpLCB0ZXN0X3NldCRDbGFzc2VzKQoKUk9DUGVyIDwtIHBlcmZvcm1hbmNlKFJPQ1ByZWQsIG1lYXN1cmU9InRwciIseC5tZWFzdXJlPSJmcHIiKQphdWMgPC0gcGVyZm9ybWFuY2UoUk9DUHJlZCwgbWVhc3VyZSA9ICJhdWMiKQphdWMgPC0gYXVjQHkudmFsdWVzW1sxXV0KYXVjCnBsb3QoUk9DUGVyLCBjb2xvcml6ZSA9IFRSVUUpCmBgYAoKCiMjIyBMREEKCkxpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMgaXMgYmVzdCB1c2VkIHdoZW4gdGhlIGRlY2lzaW9uIGJvdW5kYXJ5IG9mIG91ciBnaXZlbiBkYXRhc2V0IGlzIGFzc3VtZWQgdG8gYmUgbGluZWFyLiBUaGVyZSBhcmUgdHdvIGJhc2ljIGFzc3VtcHRpb25zIHRoYXQgTERBIHRha2VzIGludG8gY29uc2lkZXJhdGlvbjoKCjEuIFRoZXJlIGlzIGEgY29tbW9uIHZhcmlhbmNlIGFjcm9zcyBhbGwgcmVzcG9uc2UgY2xhc3NlcwoyLiBUaGUgZGlzdHJpYnV0aW9uIG9mIG9ic2VydmF0aW9ucyBpbiBlYWNoIHJlc3BvbnNlIGNsYXNzIGlzIG5vcm1hbCB3aXRoIGEgKipjbGFzcy1zcGVjaWZpYyoqIG1lYW4sIGFuZCBhICoqY29tbW9uKiogdmFyaWFuY2UKCgpTaW5jZSBMREEgYXNzdW1lcyB0aGF0IGVhY2ggaW5wdXQgdmFyaWFibGUgaGFzIHRoZSBzYW1lIHZhcmlhbmNlLCB3ZSB3aWxsIHVzZSB0aGUgc3RhbmRhcmRpemVkIGRhdGEtZnJhbWUgaW4gdGhlIHRyYWluIHRlc3Qgc3BsaXRzLiBFYWNoIHZhcmlhYmxlIGluIHRoZSBzdGFuZGFyZGl6ZWQgZGF0YS1mcmFtZSBoYXMgbWVhbiBvZiAwIGFuZCB2YXJpYW5jZSBvZiAxLgoKIyMjIyBUcmFpbmluZyB0aGUgbW9kZWwKCgpgYGB7cn0KbGRhX21vZGVsID0gbGRhKENsYXNzZXMgfiBUZW1wZXJhdHVyZStSYWluK0ZGTUMrRE1DK0RDK0lTSStCVUkrRldJK1JILCBkYXRhPXRyYWluX3NldCwgZmFtaWx5PSJiaW5vbWlhbCIpCmxkYV9tb2RlbApgYGAKCgojIyMjIFRlc3RpbmcgdGhlIG1vZGVsCgojIyMjIE9uIHRoZSB0cmFpbiBzZXQKCk9uIG91ciB0cmFpbmluZyBkYXRhLCB0aGUgbW9kZWwgcmVhY2hlZCBhbiBhY2N1cmFjeSBvZiA5NS44MSUgYW5kIGFuIGYxIHNjb3JlIG9mIDk2LjMwJSwgd2l0aCA0IGZhbHNlIHBvc2l0aXZlcyBhbmQgMSBmYWxzZSBuZWdhdGl2ZS4KCmBgYHtyfQpwcmVkc19sZGEgPSBwcmVkaWN0KGxkYV9tb2RlbCx0cmFpbl9zZXQsIHR5cGU9InJlc3BvbnNlIikKY29uZnVzaW9uTWF0cml4KHByZWRzX2xkYSRjbGFzcywgdHJhaW5fc2V0JENsYXNzZXMsCiAgICAgICAgICAgICAgICBtb2RlID0gImV2ZXJ5dGhpbmciLAogICAgICAgICAgICAgICAgcG9zaXRpdmU9IjEiKQpgYGAKCiMjIyMgT24gdGhlIHRlc3Qgc2V0CgpBcyB3ZSBjYW4gc2VlIGJlbG93LCBvdXIgdGhlIG51bWJlciBvZiBmYWxzZSBwb3NpdGl2ZXMgaXMgMSwgYW5kIHRoZSBudW1iZXIgb2YgZmFsc2UgbmVnYXRpdmVzIGlzIDEgYXMgd2VsbC4gT3VyIG1vZGVsIGFsc28geWllbGRlZCBhbiBhY2N1cmFjeSBvZiA5Ni4xNSUgYW5kIGFuIGYxIHNjb3JlIG9mIDk2LjU1JS4KCmBgYHtyfQpwcmVkc19sZGEgPSBwcmVkaWN0KGxkYV9tb2RlbCx0ZXN0X3NldCwgdHlwZT0icmVzcG9uc2UiKQpjb25mdXNpb25NYXRyaXgocHJlZHNfbGRhJGNsYXNzLCB0ZXN0X3NldCRDbGFzc2VzLAogICAgICAgICAgICAgICAgbW9kZSA9ICJldmVyeXRoaW5nIiwKICAgICAgICAgICAgICAgIHBvc2l0aXZlPSIxIikKYGBgCgojIyMjIFBvdHRpbmcgdGhlIFJPQyBjdXJ2ZQoKVGhlIEFVQyBmb3IgTERBIHdhcyA5Ni4xMCUsIHNpbWlsYXIgdG8gdGhlIG9uZSBmb3IgTG9naXN0aWMgUmVncmVzc2lvbi4KCmBgYHtyfQpST0NQcmVkIDwtIHByZWRpY3Rpb24oYXMubnVtZXJpYyhwcmVkc19sZGEkY2xhc3MpLCB0ZXN0X3NldCRDbGFzc2VzKQpST0NQZXIgPC0gcGVyZm9ybWFuY2UoUk9DUHJlZCwgbWVhc3VyZT0idHByIix4Lm1lYXN1cmU9ImZwciIpCmF1YyA8LSBwZXJmb3JtYW5jZShST0NQcmVkLCBtZWFzdXJlID0gImF1YyIpCmF1YyA8LSBhdWNAeS52YWx1ZXNbWzFdXQphdWMKcGxvdChST0NQZXIsIGNvbG9yaXplID0gVFJVRSkKYGBgCgoKIyMjIFFEQQoKUXVhZHJhdGljIERpc2NyaW1pbmFudCBBbmFseXNpcyBpcyBiZXN0IHVzZWQgd2hlbiB0aGUgZGVjaXNpb24gYm91bmRhcnkgb2Ygb3VyIGdpdmVuIGRhdGFzZXQgaXMgYXNzdW1lZCB0byBiZSBub24tbGluZWFyLiBTaW1pbGFybHkgdG8gTERBLCBRREEgbWFrZXMgdHdvIGJhc2ljIGFzc3VtcHRpb25zOiAKCjEuIFRoZXJlIGlzIGEgZGlmZmVyZW50IGNvdmFyaWFuY2UgZm9yIGVhY2ggb2YgdGhlIHJlc3BvbnNlIGNsYXNzZXMKMi4gVGhlIGRpc3RyaWJ1dGlvbiBvZiBvYnNlcnZhdGlvbnMgaW4gZWFjaCByZXNwb25zZSBjbGFzcyBpcyBub3JtYWwgd2l0aCBhICoqY2xhc3Mtc3BlY2lmaWMqKiBtZWFuLCBhbmQgYSAqKmNsYXNzLXNwZWNpZmljKiogY292YXJpYW5jZQoKCiMjIyMgVHJhaW5pbmcgdGhlIG1vZGVsCgoKYGBge3J9CnFkYV9tb2RlbCA9IHFkYShDbGFzc2VzIH4gVGVtcGVyYXR1cmUrUmFpbitGRk1DK0RNQytEQytJU0krQlVJK0ZXSStSSCwgZGF0YT10cmFpbl9zZXQpCnFkYV9tb2RlbApgYGAKCltJbnRlcnByZXRhdGlvbiBvbiB0aGUgY29lZmZpY2llbnRzXQoKIyMjIyBUZXN0aW5nIHRoZSBtb2RlbAoKIyMjIyMgT24gdGhlIHRyYWluIHNldAoKT3VyIG1vZGVsIHlpZWxkcyBhbiBhY2N1cmFjeSBvZiA5OC40MyUgYW5kIGFuIGYxIHNjb3JlIG9mIDk4LjYyJSBvbiB0aGUgdHJhaW5pbmcgc2V0LgoKYGBge3J9CnByZWRzX3FkYSA9IHByZWRpY3QocWRhX21vZGVsLHRyYWluX3NldCwgdHlwZT0icmVzcG9uc2UiKQpjb25mdXNpb25NYXRyaXgocHJlZHNfcWRhJGNsYXNzLCB0cmFpbl9zZXQkQ2xhc3NlcywKICAgICAgICAgICAgICAgIG1vZGUgPSAiZXZlcnl0aGluZyIsCiAgICAgICAgICAgICAgICBwb3NpdGl2ZT0iMSIpCmBgYAoKIyMjIyMgT24gdGhlIHRlc3Qgc2V0CgpBcyB3ZSBjYW4gc2VlIGJlbG93LCBvdXIgdGhlIG51bWJlciBvZiBmYWxzZSBwb3NpdGl2ZXMgaXMgMSwgYW5kIHRoZSBudW1iZXIgb2YgZmFsc2UgbmVnYXRpdmVzIGlzIDEuIFRoZSByZXN1bHRzIGFyZSB2ZXJ5IGdvb2QgYnV0IHRoZSBvdGhlciB3YXkgYXJvdW5kIHdvdWxkIGhhdmUgYmVlbiBiZXR0ZXIgYXMgd2UgZG8gbm90IHdhbnQgdG8gbWlzcyBhbnkgcG9zaXRpdmVzIG1lYW5pbmcgd2Ugd2FudCB0byBwcmVkaWN0IGFsbCBmaXJlcy4gT3VyIG1vZGVsIHlpZWxkZWQgYW4gZjEtc2NvcmUgb2YgOTMuMzMlIGFuZCBhbiBhY2N1cmFjeSBvZiA5Mi4zMSUuICAKCmBgYHtyfQpwcmVkc19xZGEgPSBwcmVkaWN0KHFkYV9tb2RlbCx0ZXN0X3NldCwgdHlwZT0icmVzcG9uc2UiKQpjb25mdXNpb25NYXRyaXgocHJlZHNfcWRhJGNsYXNzLCB0ZXN0X3NldCRDbGFzc2VzLAogICAgICAgICAgICAgICAgbW9kZSA9ICJldmVyeXRoaW5nIiwKICAgICAgICAgICAgICAgIHBvc2l0aXZlPSIxIikKYGBgCgojIyMjIFBvdHRpbmcgdGhlIFJPQyBjdXJ2ZQoKQWZ0ZXIgcGxvdHRpbmcgdGhlIFJPQyBjdXJ2ZSB3ZSBnb3QgYW4gQVVDIG9mIDkxLjc1JSwgd2hpY2ggaXMgd29yc2UgdGhhbiBib3RoIGxvZ2lzdGljIHJlZ3Jlc3Npb24gYW5kIExEQS4KCmBgYHtyfQoKUk9DUHJlZCA8LSBwcmVkaWN0aW9uKGFzLm51bWVyaWMocHJlZHNfcWRhJGNsYXNzKSwgdGVzdF9zZXQkQ2xhc3NlcykKUk9DUGVyIDwtIHBlcmZvcm1hbmNlKFJPQ1ByZWQsIG1lYXN1cmU9InRwciIseC5tZWFzdXJlPSJmcHIiKQphdWMgPC0gcGVyZm9ybWFuY2UoUk9DUHJlZCwgbWVhc3VyZSA9ICJhdWMiKQphdWMgPC0gYXVjQHkudmFsdWVzW1sxXV0KYXVjCnBsb3QoUk9DUGVyLCBjb2xvcml6ZSA9IFRSVUUpCmBgYAoKV2UgY2FuIG9ic2VydmUgdGhhdCBRREEgcGVyZm9ybXMgYmV0dGVyIHRoYW4gTERBIG9uIHRoZSB0cmFpbmluZyBkYXRhLCBiZWNhdXNlIGl0IGhhcyB0aGUgdGVuZGVuY3kgdG8gb3Zlci1maXQgaXQuIEhvd2V2ZXIsIExEQSBwZXJmb3JtcyBiZXR0ZXIgb24gdGhlIHRlc3RpbmcgZGF0YSBzaW5jZSBpdCBnZW5lcmFsaXplcyBiZXR0ZXIgb24gdW5zZWVuIGRhdGEgcG9pbnRzLgoKIyMjIEtOTiBDbGFzc2lmaWVyCgpJbiB0aGlzIHNlY3Rpb24sIHdlIHdpbGwgZXhwbG9yZSBLTk4ncyBwZXJmb3JtYW5jZSBvbiBvdXIgcHJvYmxlbS4gV2Ugd2lsbCB1c2UgaHlwZXIgcGFyYW1ldGVyIHR1bmluZyB0byBkZXRlcm1pbmUgdGhlIGJlc3QgbnVtYmVyIG9mIG5lYXJlc3QgbnVtYmVycyAoSykgYW5kIHdlIHdpbGwgYWxzbyB1c2UgcmVwZWF0ZWQgY3Jvc3MgdmFsaWRhdGlvbiBpbiBvdXIgdHJhaW5pbmcgZm9yIGJldHRlciBwZXJmb3JtYW5jZSBlc3RpbWF0aW9uLiAKClNpbmNlIEtOTiBpcyBhIGRpc3RhbmNlIGJhc2VkIG1vZGVsLCB3ZSB3aWxsIGhlcmUgYWdhaW4gdXNlIG91ciBub3JtYWxpemVkIGRhdGFzZXQgaW5zdGVhZCBvZiB0aGUgb3JpZ2luYWwuCgojIyMjIFRyYWluaW5nIHRoZSBtb2RlbAoKIyMjIyMgU2V0dGluZyB1cCB0aGUgQ3Jvc3MtVmFsaWRhdGlvbiBmb3IgSHlwZXJwYXJhbWV0ZXIgdHVuaW5nCgpUaGUgc3VtbWFyeUZ1bmN0aW9uIGFyZ3VtZW50IGRldGVybWluZXMgd2hpY2ggbWV0cmljIHRvIHVzZSB0byBkZXRlcm1pbmUgdGhlIHBlcmZvcm1hbmNlIG9mIGEgcGFydGljdWxhciBoeXBlcnBhcmFtZXRlciBzZXR0aW5nLiBIZXJlIHdlIHNoYWxsIHVzZSBkZWZhdWx0U3VtbWFyeSB3aGljaCBjYWxjdWxhdGVzIGFjY3VyYWN5IGFuZCBrYXBwYSBzdGF0aXN0aWMuIAoKV2UgaGF2ZSBvcHRlZCB0byBnbyB3aXRoIHRoZSByZXBlYXRlZCAxMCBmb2xkIGNyb3NzLXZhbGlkYXRpb24gbWV0aG9kIHJlcGVhdGVkIDEwIHRpbWVzLiBDbGFzc1Byb2JzIHBhcmFtZXRlciBpcyBzZXQgdG8gVFJVRSBhbmQgd2UgY2FuIHNldCB0aGUgdGhyZXNob2xkIGxhdGVyIHdoZW4gd2UgdGVzdCBvdXIgbW9kZWwgcGVyZm9ybWFuY2UuCgpgYGB7cn0KdHJhaW5pbmdfY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5RnVuY3Rpb24gPSBkZWZhdWx0U3VtbWFyeSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlciA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBlYXRzID0gMTApCmBgYAoKIyMjIyMgVHJhaW5pbmcgd2l0aCBDcm9zcy12YWxpZGF0aW9uCgpOb3cgd2UgdXNlIHRoZSB0cmFpbigpIGZ1bmN0aW9uIHRvIHBlcmZvcm0gdGhlIG1vZGVsIHRyYWluaW5nL3R1bmluZyBvZiB0aGUgayBoeXBlci1wYXJhbWV0ZXIuClRoZSByYW5nZSBvZiBrIGlzIGZyb20gMyB0byA4NSBpbiBzdGVwcyBvZiAyIG1lYW5pbmcgd2Ugd2lsbCBvbmx5IGhhdmUgb2RkIHZhbHVlcyBvZiBrIG9ubHkgYXMgaXQgaXMgYmVzdCBwcmFjdGljZSBmb3IgdGhlIEtOTiBjbHVzdGVyaW5nLgoKQW5vdGhlciB0d2VhayB0aGF0IHdlIG5lZWQgdG8gbWFrZSBvbiBvdXIgZGF0YS1zZXQgaXMgdG8gY2hhbmdlIG91ciB0YXJnZXQgdmFyaWFibGUgdmFsdWVzIHRvIHZhbGlkIFIgdmFyaWFibGUgbmFtZXMgaW4gb3JkZXIgZm9yIHRoZSBLTk4gYWxnb3JpdGhtIHRvIHdvcmsgd2l0aCBjbGFzcyBQcm9iYWJpbGl0aWVzIGFzIGVhY2ggdmFsdWVzIG9mIG91ciB0YXJnZXQgdmFyaWFibGUgd2lsbCBiZWNvbWUgYSB2YXJpYWJsZSB3aXRoIGl0cyBvd24gcHJvYmFiaWxpdHkgdmFsdWVzLiBMZWF2aW5nIHRoZSB2YWx1ZXMgYXMgezAsMX0gd2lsbCB0aHJvdyBhbiBlcnJvciBhdCB1cywgdGhlcmVmb3JlIHdlIHdpbGwgc2V0IG91ciBDbGFzc2VzIHZhcmlhYmxlIHZhbHVlcyBiYWNrIHRvICdmaXJlJyBhbmQgJ25vdF9maXJlJyBhbmQgcHJvY2VlZC4gCgpgYGB7cn0KCnRyYWluX3NldF9rbm4gPC0gdHJhaW5fc2V0CnRlc3Rfc2V0X2tubiA8LSB0ZXN0X3NldAp0cmFpbl9zZXRfa25uJENsYXNzZXMgPC0gbWFwdmFsdWVzKHRyYWluX3NldCRDbGFzc2VzLCBmcm9tPWMoMCwxKSwgdG89Yygibm90X2ZpcmUiLCJmaXJlIikpCnRlc3Rfc2V0X2tubiRDbGFzc2VzIDwtIG1hcHZhbHVlcyh0ZXN0X3NldCRDbGFzc2VzLCBmcm9tPWMoMCwxKSwgdG89Yygibm90X2ZpcmUiLCJmaXJlIikpCgpgYGAKCgpgYGB7cn0Ka25uX2N2IDwtIHRyYWluKENsYXNzZXMgfiBUZW1wZXJhdHVyZStSYWluK0ZGTUMrRE1DK0RDK0lTSStCVUkrRldJK1JILCAKICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9zZXRfa25uLAogICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsCiAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbmluZ19jb250cm9sLAogICAgICAgICAgICAgICAgbWV0cmljID0gIkFjY3VyYWN5IiwKICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShrID0gc2VxKDMsIDg1LCBieSA9IDIpKSkKa25uX2N2CmBgYAoKIyMjIyMgRGlzdHJpYnV0aW9uIG9mIHByZWRpY3RlZCBwcm9iYWJpbGl0ZXMgLXRocmVzaG9sZCBpbnNwZWN0aW9uLQoKSW5zcGVjdGluZyB0aGUgcHJvYmFiaWxpdGllcyByZXZlYWxzIHRoYXQgYSBjdXRvZmYgcHJvYmFiaWxpdHkgYXJvdW5kIDAuNSBnaXZlcyB0aGUgYmVzdCBjbGFzc2lmaWNhdGlvbiByZXN1bHRzLiBJbiB0aGUgZnVuY3Rpb24gKnByZWRpY3QqIHRoZSBjdXRvZmYgaXMgc2V0IHRvIGJlIDAuNSBieSBkZWZhdWx0IHdoaWNoIG1lYW5zIHdlIGRvIG5vdCBuZWVkIHRvIGNoYW5nZSBpdC4KCmBgYHtyfQpwcmVkc19rbm4gPSBwcmVkaWN0KGtubl9jdix0cmFpbl9zZXQsIHR5cGUgPSAicHJvYiIpCnRyYWluX3NldCAlPiUKICBnZ3Bsb3QoKSArCiAgYWVzKHggPSBwcmVkc19rbm4kZmlyZSwgZmlsbCA9IENsYXNzZXMpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMjApICsKICBsYWJzKHggPSAiUHJvYmFiaWxpdHkiLCB5ID0gIkNvdW50IiwgdGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGZvciB2YWx1ZSBmaXJlIiApCmBgYAoKIyMjIyBUZXN0aW5nIHRoZSBtb2RlbAoKV2hlbiB0ZXN0aW5nIG91ciBtb2RlbCBvbiB0aGUgdGVzdCBzZXQgaG93ZXZlciwgd2UgZ2V0IGFuIGFjY3VyYWN5IG9mIDg2LjU0JSBhbmQgYW4gZjEgc2NvcmUgb2YgODcuNzIlCgpgYGB7cn0KcHJlZHNfa25uID0gcHJlZGljdChrbm5fY3YsdGVzdF9zZXRfa25uLCB0eXBlPSJyYXciKQpjb25mdXNpb25NYXRyaXgocHJlZHNfa25uLCB0ZXN0X3NldF9rbm4kQ2xhc3NlcywKICAgICAgICAgICAgICAgIG1vZGUgPSAiZXZlcnl0aGluZyIsCiAgICAgICAgICAgICAgICBwb3NpdGl2ZT0iZmlyZSIpCmBgYAoKIyMjIyBQb3R0aW5nIHRoZSBST0MgY3VydmUKCkFmdGVyIHBsb3R0aW5nIHRoZSBST0MgY3VydmUsIHdlIGdldCBhbiBBVUMgb2YgODYuNTglIHdoaWNoIGlzIHRoZSB3b3JzdCBvdXQgb2YgYWxsIG9mIG91ciBtb2RlbHMgc28gZmFyLgoKYGBge3J9CnByZWRzIDwtIHByZWRpY3Rpb24oYXMubnVtZXJpYyhwcmVkc19rbm4pLCBhcy5udW1lcmljKHRlc3Rfc2V0X2tubiRDbGFzc2VzKSkgCnBlcmYgPC0gcGVyZm9ybWFuY2UocHJlZHMsInRwciIsImZwciIpCmF1YyA8LSBwZXJmb3JtYW5jZShwcmVkcywgbWVhc3VyZSA9ICJhdWMiKQphdWMgPC0gYXVjQHkudmFsdWVzW1sxXV0KYXVjIApwbG90KHBlcmYsY29sb3JpemU9VFJVRSkKYGBgCiAKIyMjIEVuc2VtYmxlIE1ldGhvZHMgLyBUcmVlIGJhc2VkIG1ldGhvZHMKCiMjIyMgU2ltcGxlIERlY2lzaW9uIFRyZWVzIGFuZCB0cmVlIHBydW5pbmcKClRoZSBnb2FsIG9mIGVuc2VtYmxlIG1vZGVsaW5nIGlzIHRvIGltcHJvdmUgcGVyZm9ybWFuY2Ugb3ZlciBhIGJhc2VsaW5lIG1vZGVsIGJ5IGNvbWJpbmluZyBtdWx0aXBsZSBtb2RlbHMuIFNvLCB3ZSB3aWxsIHNldCB0aGUgYmFzZWxpbmUgcGVyZm9ybWFuY2UgbWVhc3VyZSBieSBzdGFydGluZyB3aXRoIG9uZSBhbGdvcml0aG0uIEluIG91ciBjYXNlLCB3ZSB3aWxsIGJ1aWxkIGEgc2ltcGxlIGRlY2lzaW9uIHRyZWUuCgpEZWNpc2lvbiB0cmVlcyBhcmUgd2lkZWx5IHVzZWQgY2xhc3NpZmllcnMgaW4gaW5kdXN0cmllcyBiYXNlZCBvbiB0aGVpciB0cmFuc3BhcmVuY3kgaW4gZGVzY3JpYmluZyBydWxlcyB0aGF0IGxlYWQgdG8gYSBwcmVkaWN0aW9uLiBUaGV5IGFyZSBhcnJhbmdlZCBpbiBhIGhpZXJhcmNoaWNhbCB0cmVlLWxpa2Ugc3RydWN0dXJlIGFuZCBhcmUgc2ltcGxlIHRvIHVuZGVyc3RhbmQgYW5kIGludGVycHJldC4gVGhleSBhcmUgbm90IHN1c2NlcHRpYmxlIHRvIG91dGxpZXJzIGFuZCBhcmUgYWJsZSB0byBjYXB0dXJlIG5vbmxpbmVhciByZWxhdGlvbnNoaXBzLgoKV2Ugd2lsbCBiZSB1c2luZyB0aGUgKnJwYXJ0KiBsaWJyYXJ5IGZvciBjcmVhdGluZyBkZWNpc2lvbiB0cmVlcy4gKHJwYXJ0KSBzdGFuZHMgZm9yIHJlY3Vyc2l2ZSBwYXJ0aXRpb25pbmcgYW5kIGVtcGxveXMgdGhlIENBUlQgKGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHRyZWVzKSBhbGdvcml0aG0uIEFwYXJ0IGZyb20gdGhlICpycGFydCogbGlicmFyeSwgdGhlcmUgYXJlIG1hbnkgb3RoZXIgZGVjaXNpb24gdHJlZSBsaWJyYXJpZXMgbGlrZSAqQzUwKiwgKlBhcnR5KiwgKlRyZWUqLCBhbmQgKm1hcFRyZWUuKgoKYGBge3IsIHJlc3VsdHM9J2hpZGUnfQpsaWJyYXJ5KHJwYXJ0KQpsaWJyYXJ5KHJwYXJ0LnBsb3QpCmBgYAoKIyMjIyBUcmFpbmluZyB0aGUgbW9kZWwgd2l0aG91dCB0aGUgcHJ1bmluZwoKTmV4dCwgd2UgY3JlYXRlIGEgZGVjaXNpb24gdHJlZSBtb2RlbCBieSBjYWxsaW5nIHRoZSAqcnBhcnQqIGZ1bmN0aW9uLiBMZXQncyBmaXJzdCBjcmVhdGUgYSBiYXNlIG1vZGVsIHdpdGggZGVmYXVsdCBwYXJhbWV0ZXJzIGFuZCB2YWx1ZS4gTm90aWNlIHRoYXQgd2UgZG8gbm90IGluY2x1ZGUgYW55IHRyYWluIGNvbnRyb2wgbWVhbmluZyB0aGF0IHdlIGFyZSBub3QgdXNpbmcgYW55IGJhZ2dpbmcsIGNyb3NzIHZhbGlkYXRpb24gb3IgcHJ1bmluZyB0ZWNobmlxdWVzLiBUaGUgcmVzdWx0aW5nIHRyZWUgaXMgYSBzaW1wbGUgZGVjaXNpb24gdHJlZS4gV2Ugd2lsbCBleHBsb3JlIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbW9kZWwgb24gdGhlIHRyYWluIGFuZCB0ZXN0IHNldHMgbmV4dC4KCmBgYHtyfQpiYXNlX21vZGVsIDwtIHJwYXJ0KENsYXNzZXMgfiBUZW1wZXJhdHVyZStSYWluK0ZGTUMrRE1DK0RDK0lTSStCVUkrRldJK1JILCBkYXRhID0gdHJhaW5fc2V0LCBtZXRob2QgPSAiY2xhc3MiKQpzdW1tYXJ5KGJhc2VfbW9kZWwpCiNQbG90IERlY2lzaW9uIFRyZWUKcnBhcnQucGxvdChiYXNlX21vZGVsKQpgYGAKQWZ0ZXIgZXhwbG9yaW5nIHRoZSBjb25mdXNpb24gbWF0cml4IGFuZCB0aGUgZGlmZmVyZW50IHBlcmZvcm1hbmNlIG1ldHJpY3MsIHdlIGNhbiBzZWUgdGhhdCBvdXIgYmFzZSBkZWNpc2lvbiB0cmVlIGRvZXMgbm90IGZpdCB0aGUgZGF0YSBwZXJmZWN0bHkgYW5kIGhhcyAzIG1pc3MtY2xhc3NpZmljYXRpb25zIG9uIHRoZSB0cmFpbmluZyBzZXQuIFRob3NlIDMgZmFsc2UgcG9zaXRpdmVzIGNhdXNlZCB0aGUgbW9kZWwncyBhY2N1cmFjeSB0byBiZSA5OC40MyUgYW5kIHRoZSBmMS1zY29yZSB0byBiZSA5OC42MyUuCgpgYGB7cn0KcHJlZHNfdHJlZSA9IHByZWRpY3QoYmFzZV9tb2RlbCx0cmFpbl9zZXQsIHR5cGU9ImNsYXNzIikKY29uZnVzaW9uTWF0cml4KHByZWRzX3RyZWUsIHRyYWluX3NldCRDbGFzc2VzLCBtb2RlID0gImV2ZXJ5dGhpbmciLCBwb3NpdGl2ZT0nMScpCmBgYAoKIyMjIyBUZXN0aW5nIHRoZSB1bnBydW5lZCBtb2RlbAoKT3VyIGJhc2UgZGVjaXNpb24gdHJlZSBwZXJmb3JtcyB2ZXJ5IHdlbGwgb24gdW5zZWVuIGRhdGEgd2l0aCBqdXN0IG9uZSBmYWxzZSBwb3NpdGl2ZSwgYW4gYWNjdXJhY3kgb2YgOTglIGFuZCBhbiBmMS1zY29yZSBvZiA5OC4zMSUuIAoKYGBge3J9CnByZWRzX3VucHJ1bmVkID0gcHJlZGljdChiYXNlX21vZGVsLHRlc3Rfc2V0LCB0eXBlPSJjbGFzcyIpCmNvbmZ1c2lvbk1hdHJpeChwcmVkc191bnBydW5lZCwgdGVzdF9zZXQkQ2xhc3NlcywKICAgICAgICAgICAgICAgIG1vZGUgPSAiZXZlcnl0aGluZyIsCiAgICAgICAgICAgICAgICBwb3NpdGl2ZT0nMScpCgpgYGAKIyMjIyBST0MgY3VydmUgZm9yIHVucHJ1bmVkIG1vZGVsCgpUaGlzIG1vZGVsIGdpdmVzIHVzIGFuIEFVQyBvZiA5Ny44MiUKCmBgYHtyfQpwcmVkcyA8LSBwcmVkaWN0aW9uKGFzLm51bWVyaWMocHJlZHNfdW5wcnVuZWQpLCB0ZW1wX3Rlc3Rfc2V0LmNsYXNzZXMpIApwZXJmIDwtIHBlcmZvcm1hbmNlKHByZWRzLCJ0cHIiLCJmcHIiKQphdWMgPC0gcGVyZm9ybWFuY2UocHJlZHMsIG1lYXN1cmUgPSAiYXVjIikKYXVjIDwtIGF1Y0B5LnZhbHVlc1tbMV1dCmF1YyAKcGxvdChwZXJmLGNvbG9yaXplPVRSVUUpCmBgYAoKIyMjIyBUcmFpbmluZyB0aGUgbW9kZWwgd2l0aCBwcnVuaW5nCgpQcmUtcHJ1bmluZyBpcyBhbHNvIGtub3duIGFzIGVhcmx5IHN0b3BwaW5nIGNyaXRlcmlhLiBBcyB0aGUgbmFtZSBzdWdnZXN0cywgdGhlIGNyaXRlcmlhIGFyZSBzZXQgYXMgcGFyYW1ldGVyIHZhbHVlcyB3aGlsZSBidWlsZGluZyB0aGUgcnBhcnQgbW9kZWwuIEJlbG93IGFyZSBzb21lIG9mIHRoZSBwcmUtcHJ1bmluZyBjcml0ZXJpYSB0aGF0IGNhbiBiZSB1c2VkLiBUaGUgdHJlZSBzdG9wcyBncm93aW5nIHdoZW4gaXQgbWVldHMgYW55IG9mIHRoZXNlIHByZS1wcnVuaW5nIGNyaXRlcmlhLCBvciBpdCBkaXNjb3ZlcnMgdGhlIHB1cmUgY2xhc3Nlcy4KClRoZSBjb21wbGV4aXR5IHBhcmFtZXRlciAoY3ApIGluIHJwYXJ0IGlzIHRoZSBtaW5pbXVtIGltcHJvdmVtZW50IGluIHRoZSBtb2RlbCBuZWVkZWQgYXQgZWFjaCBub2RlLiBJdCBpcyBiYXNlZCBvbiB0aGUgY29zdCBjb21wbGV4aXR5IG9mIHRoZSBtb2RlbCBhbmQgd29ya3MgYXMgZm9sbG93czogCgotIEZvciB0aGUgZ2l2ZW4gdHJlZSwgYWRkIHVwIHRoZSBtaXNzY2xhc3NpZmljYXRpb24gYXQgZXZlcnkgdGVybWluYWwgbm9kZS4KLSBUaGVuIG11bHRpcGx5IHRoZSBudW1iZXIgb2Ygc3BsaXRzIHRpbWUgYSBwZW5hbHR5IHRlcm0gKGxhbWJkYSkgYW5kIGFkZCBpdCB0byB0aGUgdG90YWwgbWlzc2NsYXNzaWZpY2F0aW9uLgotIFRoZSBsYW1iZGEgaXMgZGV0ZXJtaW5lZCB0aHJvdWdoIGNyb3NzLXZhbGlkYXRpb24gYW5kIG5vdCByZXBvcnRlZCBpbiBSLgotIFRoZSBjcCB3ZSBzZWUgdXNpbmcgcHJpbnRjcCgpIGlzIHRoZSBzY2FsZWQgdmVyc2lvbiBvZiBsYW1iZGEgb3ZlciB0aGUgbWlzY2xhc3NpZmNhdGlvbiByYXRlIG9mIHRoZSBvdmVyYWxsIGRhdGEuCgpUaGUgY3AgdmFsdWUgaXMgYSBzdG9wcGluZyBwYXJhbWV0ZXIuIEl0IGhlbHBzIHNwZWVkIHVwIHRoZSBzZWFyY2ggZm9yIHNwbGl0cyBiZWNhdXNlIGl0IGNhbiBpZGVudGlmeSBzcGxpdHMgdGhhdCBkb27DrXQgbWVldCB0aGlzIGNyaXRlcmlhIGFuZCBwcnVuZSB0aGVtIGJlZm9yZSBnb2luZyB0b28gZmFyLgoKT3RoZXIgcGFyYW1ldGVycyBpbmNsdWRlIGJ1dCBhcmUgbm90IGxpbWl0ZWQgdG86IAoKLSBtYXhkZXB0aDogVGhpcyBwYXJhbWV0ZXIgaXMgdXNlZCB0byBzZXQgdGhlIG1heGltdW0gZGVwdGggb2YgYSB0cmVlLiBJbiB0aGlzIHByZXBydW5pbmcgc3RlcC4KCi0gbWluc3BsaXQ6IEl0IGlzIHRoZSBtaW5pbXVtIG51bWJlciBvZiByZWNvcmRzIHRoYXQgbXVzdCBleGlzdCBpbiBhIG5vZGUgZm9yIGEgc3BsaXQgdG8gaGFwcGVuIG9yIGJlIGF0dGVtcHRlZC4KCkFuZCBvbmUgbGFzdCB0aGluZywgc2luY2Ugd2UgYXJlIGluIGEgY2xhc3NpZmljYXRpb24gc2V0dGluZywgd2UgaGF2ZSB0byBzcGVjaWZ5IGNsYXNzIGFzIHRoZSBtZXRob2QgdXNlZCBmb3IgYnVpbGRpbmcgb3VyIHRyZWUgaW5zdGVhZCBvZiAnYW5vdmEnIHRoYXQgaXMgdXNlZCBpbiByZWdyZXNzaW9uIHNldHRpbmdzLiAKCmBgYHtyfQpwcnVuZWRfYmFzZV9tb2RlbCA8LSBycGFydChDbGFzc2VzIH4gVGVtcGVyYXR1cmUrUmFpbitGRk1DK0RNQytEQytJU0krQlVJK0ZXSStSSCwgZGF0YSA9IHRyYWluX3NldCwgbWV0aG9kID0gImNsYXNzIiwgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKGNwID0gMCwgbWF4ZGVwdGggPSA4LCBtaW5zcGxpdCA9IDgpKQpzdW1tYXJ5KHBydW5lZF9iYXNlX21vZGVsKQojUGxvdCBEZWNpc2lvbiBUcmVlCnByaW50Y3AocHJ1bmVkX2Jhc2VfbW9kZWwpCnJwYXJ0LnBsb3QocHJ1bmVkX2Jhc2VfbW9kZWwpCmBgYApUaGUgc3VtbWFyeSBvZiBvdXIgYmFzZSBtb2RlbCB3aWxsIGdpdmUgdXMgdGhlIGRldGFpbHMgb2YgZWFjaCBzcGxpdCB3aXRoIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zLCB0aGUgdmFsdWUgb2YgdGhlIGNvbXBsZXhpdHkgcGFyYW1ldGVyLCB0aGUgcHJlZGljdGVkIGNsYXNzLCB0aGUgY2xhc3MgY291bnRzIHdpdGggdGhlaXIgcHJvYmFiaWxpdGllcyBhbmQgdGhlIGNoaWxkcmVuIG9mIHRoZSBub2RlLiBJdCB3aWxsIGFsc28gZ2l2ZSBkZXRhaWxzIGFib3V0IHRoZSBmdXR1cmUgc3BsaXRzIHN0YXJ0aW5nIHdpdGggdGhlIHByaW1hcnkgc3BsaXRzIHRoYXQgd2lsbCBmb2xsb3cgYW5kIHRoZSBwZXJjZW50IGltcHJvdmVtZW50IGluIHRoZSBwcmVkaWN0aW9uIGFzIHdlbGwgYXMgdGhlIHN1cnJvZ2F0ZSBzcGxpdHMgdGhhdCBjb21lIGxhdGVyIG9uLiAKClRoZSByZXN1bHRpbmcgdHJlZSBhcyBleHBsYWluZWQgaW4gdGhlIGFib3ZlIHNlY3Rpb24sIGlzIHRoZSBzbWFsbGVzdCB0cmVlIHdpdGggdGhlIGxvd2VzdCBtaXNzLWNsYXNzaWZpY2F0aW9uIGxvc3MuIFRoaXMgdHJlZSBpcyBwbG90dGVkIHdpdGggdGhlIHNwbGl0IGRldGFpbHMgYW5kIGxlYWYgbm9kZSBjbGFzc2VzLiAKClRoZSBvcHRpbWFsIENQIHZhbHVlIGZvdW5kIHdhcyAwLjAxMjA0OCAKCmBgYHtyfQpwcmVkcyA9IHByZWRpY3QocHJ1bmVkX2Jhc2VfbW9kZWwsdHJhaW5fc2V0LCB0eXBlPSJjbGFzcyIpCmNvbmZ1c2lvbk1hdHJpeChwcmVkcywgdHJhaW5fc2V0JENsYXNzZXMsCiAgICAgICAgICAgICAgICBtb2RlID0gImV2ZXJ5dGhpbmciLAogICAgICAgICAgICAgICAgcG9zaXRpdmU9JzEnKQpgYGAKClRoZSB0cmFpbiBhY2N1cmFjeSBvZiBvdXIgdHJlZSBpcyA5OC45NSUgd2l0aCBhbiBmMSBzY29yZSBvZiA5OSUgYXMgd2VsbCB3aXRoIGEgdG90YWwgb2YgMiBtaXNzY2xhc3NpZmljYXRpb25zLCAxIEZQIGFuZCAxIEZOLiAgCgojIyMjIyBUZXN0aW5nIHRoZSBwcnVuZWQgbW9kZWwKCldlIGhhdmUgYSA5Ni4xNSUgYWNjdXJhY3kgb24gb3VyIGhlbGQtb3V0IHZhbGlkYXRpb24gc2V0IHdoaWNoIG1lYW5zIHdlIGhhdmUgc3VjY2Vzc2Z1bGx5IGF2b2lkZWQgb3Zlci1maXR0aW5nIHVzaW5nIHRyZWUgcHJ1bmluZy4gCgpgYGB7cn0KcHJlZHMgPSBwcmVkaWN0KHBydW5lZF9iYXNlX21vZGVsLHRlc3Rfc2V0LCB0eXBlPSJjbGFzcyIpCmNvbmZ1c2lvbk1hdHJpeChwcmVkcywgdGVzdF9zZXQkQ2xhc3NlcywKICAgICAgICAgICAgICAgIG1vZGUgPSAiZXZlcnl0aGluZyIsCiAgICAgICAgICAgICAgICBwb3NpdGl2ZT0nMScpCmBgYAojIyMjIyBST0MgY3VydmUgZm9yIHBydW5lZCBtb2RlbAoKVGhlIEFVQyB3YXMgOTYuNTUlCgpgYGB7cn0KcHJlZHMgPC0gYXMudmVjdG9yKHByZWRzLCBtb2RlID0gIm51bWVyaWMiKQp0ZXN0X3NldCRDbGFzc2VzIDwtIGFzLnZlY3Rvcih0ZXN0X3NldCRDbGFzc2VzLCBtb2RlID0gIm51bWVyaWMiKQpwcmVkIDwtIHByZWRpY3Rpb24ocHJlZHMsIHRlc3Rfc2V0JENsYXNzZXMpIAphdWMgPC0gcGVyZm9ybWFuY2UocHJlZCwgbWVhc3VyZSA9ICJhdWMiKQphdWMgPC0gYXVjQHkudmFsdWVzW1sxXV0KYXVjIApwZXJmIDwtIHBlcmZvcm1hbmNlKHByZWQsInRwciIsImZwciIpCnBsb3QocGVyZixjb2xvcml6ZT1UUlVFKQpgYGAKCgoKIyMjIEJBR0dJTkcKCkJhZ2dpbmcsIG9yIGJvb3RzdHJhcCBhZ2dyZWdhdGlvbiwgaXMgYW4gZW5zZW1ibGUgbWV0aG9kIHRoYXQgaW52b2x2ZXMgdHJhaW5pbmcgdGhlIHNhbWUgYWxnb3JpdGhtIG1hbnkgdGltZXMgYnkgdXNpbmcgZGlmZmVyZW50IHN1YnNldHMgc2FtcGxlZCBmcm9tIHRoZSB0cmFpbmluZyBkYXRhLiBUaGUgZmluYWwgb3V0cHV0IHByZWRpY3Rpb24gaXMgdGhlbiBhdmVyYWdlZCBhY3Jvc3MgdGhlIHByZWRpY3Rpb25zIG9mIGFsbCB0aGUgc3ViLW1vZGVscy4gVGhlIHR3byBtb3N0IHBvcHVsYXIgYmFnZ2luZyBlbnNlbWJsZSB0ZWNobmlxdWVzIGFyZSBCYWdnZWQgRGVjaXNpb24gVHJlZXMgYW5kIFJhbmRvbSBGb3Jlc3QuCgojIyMjIEJhZ2dlZCBEZWNpc2lvbiBUcmVlcwoKVGhpcyBtZXRob2QgcGVyZm9ybXMgYmVzdCB3aXRoIGFsZ29yaXRobXMgdGhhdCBoYXZlIGhpZ2ggdmFyaWFuY2UuIFRoZSBhcmd1bWVudCAqbWV0aG9kPSJ0cmVlYmFnIiogc3BlY2lmaWVzIHRoZSBhbGdvcml0aG0uIFdlIHdpbGwgdHJhaW4gb3VyIG1vZGVsIHVzaW5nIGEgNS1mb2xkIGNyb3NzIHZhbGlkYXRpb24gcmVwZWF0ZWQgNSB0aW1lcy4gVGhlIHNhbXBsaW5nIHN0cmF0ZWd5IHVzZWQgZm9yIHRoZSBiYWdnZWQgdHJlZXMgaXMgUk9TRS4KCiMjIyMjIFRyYWluaW5nIHRoZSBtb2RlbAoKYGBge3J9CmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0icmVwZWF0ZWRjdiIsIG51bWJlcj01LCByZXBlYXRzPTUpCgpiYWdDQVJUX21vZGVsIDwtIHRyYWluKENsYXNzZXMgfiBUZW1wZXJhdHVyZStSYWluK0ZGTUMrRE1DK0RDK0lTSStCVUkrRldJK1JILCBkYXRhPXRyYWluX3NldCwgbWV0aG9kPSJ0cmVlYmFnIiwgbWV0cmljPSJBY2N1cmFjeSIsIHRyQ29udHJvbD1jb250cm9sKQpgYGAKCiMjIyMjIFRyYWluaW5nIGFjY3VyYWN5CgpXZSBhY2hpZXZlZCBhIHBlcmZlY3QgZml0IHVzaW5nIGJhZ2dlZCB0cmVlcyB0cmFpbmVkIHVzaW5nIGEgNS1mb2xkIENWIHJlcGVhdGVkIDUgdGltZXMuCgpgYGB7cn0KcHJlZHMgPSBwcmVkaWN0KGJhZ0NBUlRfbW9kZWwsdHJhaW5fc2V0LCB0eXBlPSJyYXciKQpjb25mdXNpb25NYXRyaXgocHJlZHMsIHRyYWluX3NldCRDbGFzc2VzLAogICAgICAgICAgICAgICAgbW9kZSA9ICJldmVyeXRoaW5nIiwKICAgICAgICAgICAgICAgIHBvc2l0aXZlPScxJykKYGBgCiMjIyMjIFRlc3RpbmcgYWNjdXJhY3kKClRoZSBiYWdnZWQgbW9kZWwgZGlkIG5vdCBhY2hpZXZlIGEgcGVyZmVjdCBwZXJmb3JtYW5jZSBvbiB1bnNlZW4gZGF0YSB3aGljaCBsZWFkcyB1cyB0byBiZWxpZXZlIGl0IG92ZXIgZml0IHRoZSBkYXRhLiBUaGlzIGNhbiBiZSBjYXVzZWQgYnkgdGhlIGZhY3QgdGhhdCB0aGUgYmFnZ2VkIHRyZWVzIHdlcmUgaGlnaGx5IGNvcnJlbGF0ZWQgYmV0d2VlbiBlYWNoIG90aGVyIHdoaWNoIGNvdWxkIGJlIGR1ZSB0byB0aGUgYWJzZW5jZSBvZiByYW5kb21pemF0aW9uIGluIHRoZSBmZWF0dXJlcyB1c2VkIGZvciBlYWNoIGJhZ2dlZCB0cmVlLiBXaGF0IGhhcHBlbmVkIG1vc3QgcHJvYmFibHkgaXMgdGhlIHVzZSBvZiB0aGUgc2FtZSBzdHJvbmcgcHJlZGljdG9ycyBpbiBhbGwgYmFnZ2VkIHRyZWVzIGNhdXNpbmcgdGhpcyBoaWdoIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlbS4gSW4gb3JkZXIgdG8gZ2V0IHJpZCBvZiBpdCB3ZSB3aWxsIGltcGxlbWVudCBSYW5kb20gRm9yZXN0cyBuZXh0IHdoaWNoIGFkZHMgdGhpcyByYW5kb21pemF0aW9uIGluIHRoZSBmZWF0dXJlcyBzZWxlY3RlZCBmb3IgZWFjaCBiYWdnZWQgdHJlZS4gVGhlIGFjY3VyYWN5IHdlIGdvdCBpcyA5OCUKCmBgYHtyfQpwcmVkcyA9IHByZWRpY3QoYmFnQ0FSVF9tb2RlbCx0ZXN0X3NldCwgdHlwZT0icmF3IikKcHJlZHMgPC0gbWFwdmFsdWVzKHByZWRzLCBmcm9tPWMoMCwxKSwgdG89YygxLDIpKQpjb25mdXNpb25NYXRyaXgocHJlZHMsIGFzLmZhY3Rvcih0ZXN0X3NldCRDbGFzc2VzKSwKICAgICAgICAgICAgICAgIG1vZGUgPSAiZXZlcnl0aGluZyIsCiAgICAgICAgICAgICAgICBwb3NpdGl2ZT0nMScpCmBgYAoKIyMjIFJhbmRvbSBGb3Jlc3RzCgpSYW5kb20gRm9yZXN0IGlzIGFuIGV4dGVuc2lvbiBvZiBiYWdnZWQgZGVjaXNpb24gdHJlZXMsIHdoZXJlIGluIGFkZGl0aW9uIHRvIHNhbXBsaW5nIHRoZSBkYXRhLCB3ZSBhbHNvIHNhbXBsZSB0aGUgdmFyaWFibGVzIGluIGVhY2ggYmFnZ2VkIGRlY2lzaW9uIHRyZWUuIFRoZSB0cmVlcyBhcmUgY29uc3RydWN0ZWQgd2l0aCB0aGUgb2JqZWN0aXZlIG9mIHJlZHVjaW5nIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBpbmRpdmlkdWFsIGRlY2lzaW9uIHRyZWVzIGJ5IG1ha2luZyBzdXJlIHdlIGRvIG5vdCB1c2UgdGhlIHNhbWUgc3Ryb25nIHByZWRpY3RvcnMgaW4gYWxsIGJhZ2dlZCB0cmVlcyByZXN1bHRpbmcgaW4gc3Ryb25nbHkgY29ycmVsYXRlZCB0cmVlcy4KCiMjIyMgVHJhaW5pbmcgdGhlIG1vZGVsCgpgYGB7cn0KY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJyZXBlYXRlZGN2IiwgbnVtYmVyPTUsIHJlcGVhdHM9NSkKCnJmX21vZGVsIDwtIHRyYWluKENsYXNzZXMgfiBUZW1wZXJhdHVyZStSYWluK0ZGTUMrRE1DK0RDK0lTSStCVUkrRldJK1JILCBkYXRhPXRyYWluX3NldCwgbWV0aG9kPSJyZiIsIG1ldHJpYz0iQWNjdXJhY3kiLCB0ckNvbnRyb2w9Y29udHJvbCkKYGBgCgojIyMjIFRyYWluaW5nIGFjY3VyYWN5CgpPbmNlIGFnYWluLCBvdXIgcmFuZG9tIGZvcmVzdCBtb2RlbCBhY2hpZXZlZCBhIHBlcmZlY3QgZmlyc3Qgd2l0aCBhIDUtZm9sZCBDViByZXBlYXRlZCA1IHRpbWVzLiAKCmBgYHtyfQpwcmVkcyA9IHByZWRpY3QocmZfbW9kZWwsdHJhaW5fc2V0LCB0eXBlPSJyYXciKQpjb25mdXNpb25NYXRyaXgocHJlZHMsIHRyYWluX3NldCRDbGFzc2VzLAogICAgICAgICAgICAgICAgbW9kZSA9ICJldmVyeXRoaW5nIiwKICAgICAgICAgICAgICAgIHBvc2l0aXZlPScxJykKYGBgCgojIyMjIFRlc3RpbmcgYWNjdXJhY3kKCk9uIG91ciBkYXRhc2V0LCB0aGUgcmFuZG9tIGZvcmVzdCBkaWQgbm90IHBlcmZvcm0gYmV0dGVyIHRoYXQgdGhlIHByZXZpb3VzIG1vZGVscywgeWllbGRpbmcgYW4gYWNjdXJhY3kgb2YgOTYuMTUlLiBTbyBmYXIsIEJBR0dJTkcgaGFzIGJlZW4gdGhlIGJlc3QgbWV0aG9kLgoKYGBge3J9CnByZWRzID0gcHJlZGljdChyZl9tb2RlbCx0ZXN0X3NldCwgdHlwZT0icmF3IikKcHJlZHMgPC0gbWFwdmFsdWVzKHByZWRzLCBmcm9tPWMoMCwxKSwgdG89YygxLDIpKQpjb25mdXNpb25NYXRyaXgocHJlZHMsIGFzLmZhY3Rvcih0ZXN0X3NldCRDbGFzc2VzKSwKICAgICAgICAgICAgICAgIG1vZGUgPSAiZXZlcnl0aGluZyIsCiAgICAgICAgICAgICAgICBwb3NpdGl2ZT0nMScpCmBgYAoKIyMjIEJPT1NUSU5HCgpJbiBib29zdGluZywgbXVsdGlwbGUgbW9kZWxzIGFyZSB0cmFpbmVkIHNlcXVlbnRpYWxseSBhbmQgZWFjaCBtb2RlbCBsZWFybnMgZnJvbSB0aGUgZXJyb3JzIG9mIGl0cyBwcmVkZWNlc3NvcnMuIFdlIHdpbGwgdXNlIHRoZSBTdG9jaGFzdGljIEdyYWRpZW50IEJvb3N0aW5nIGFsZ29yaXRobS4KCiMjIyMgU3RvY2hhc3RpYyBHcmFkaWVudCBCb29zdGluZwoKIyMjIyBUcmFpbmluZyB0aGUgbW9kZWwKQW4gaW1wb3J0YW50IHRoaW5nIHRvIG5vdGUgaXMgdGhhdCBzdG9jaGFzdGljIGdyYWRpZW50IGJvb3N0aW5nIHRha2VzIGEgbXVjaCBsb25nZXIgdGltZSB0byB0cmFpbiBhcyBpdCBpcyBhIHN0ZXAtd2lzZSBtZXRob2Qgd2hpY2ggdGFrZXMgYSBsb3Qgb2YgaXRlcmF0aW9ucyB0byBjb252ZXJnZS4gQWRkaW5nIGNyb3NzLXZhbGlkYXRpb24gbWFrZXMgaXQgZXZlbiBsb25nZXIuIAoKYGBge3IsIHJlc3VsdHM9ImhpZGUifQpjb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTUpCgpTR0IgPC0gdHJhaW4oQ2xhc3NlcyB+IFRlbXBlcmF0dXJlK1JhaW4rRkZNQytETUMrREMrSVNJK0JVSStGV0krUkgsIGRhdGE9dHJhaW5fc2V0LCBtZXRob2Q9ImdibSIsIG1ldHJpYz0iQWNjdXJhY3kiLCB0ckNvbnRyb2w9Y29udHJvbCkKYGBgCiMjIyMjIFRyYWluaW5nIGFjY3VyYWN5CgoxMDAlIGFjY3VyYWN5IG9uIHRyYWluaW5nIGRhdGEuCgpgYGB7cn0KcHJlZHMgPSBwcmVkaWN0KFNHQix0cmFpbl9zZXQsIHR5cGU9InJhdyIpCmNvbmZ1c2lvbk1hdHJpeChwcmVkcywgdHJhaW5fc2V0JENsYXNzZXMsCiAgICAgICAgICAgICAgICBtb2RlID0gImV2ZXJ5dGhpbmciLAogICAgICAgICAgICAgICAgcG9zaXRpdmU9JzEnKQpgYGAKCiMjIyMjIFRlc3RpbmcgYWNjdXJhY3kKCjk4LjA4JSBhY2N1cmFjeSBvbiB1bnNlZW4gZGF0YSwgc2FtZSBhcyB0aGUgdHJhaW5pbmcgc2V0LCBib29zdGluZyBicm91Z2h0IG11Y2ggaW1wcm92ZW1lbnQgb3ZlciBvdXIgcmFuZG9tIGZvcmVzdCBtb2RlbCBldmVuIHRob3VnaCBpdCB0b29rIGEgZ3JlYXRlciB0cmFpbmluZyB0aW1lLgoKYGBge3J9CnByZWRzID0gcHJlZGljdChTR0IsdGVzdF9zZXQsIHR5cGU9InJhdyIpCnByZWRzIDwtIG1hcHZhbHVlcyhwcmVkcywgZnJvbT1jKDAsMSksIHRvPWMoMSwyKSkKY29uZnVzaW9uTWF0cml4KHByZWRzLCBhcy5mYWN0b3IodGVzdF9zZXQkQ2xhc3NlcyksCiAgICAgICAgICAgICAgICBtb2RlID0gImV2ZXJ5dGhpbmciLAogICAgICAgICAgICAgICAgcG9zaXRpdmU9JzEnKQpgYGAKCiMjIyMgRmluYWwgbm90ZXMgb24gRW5zZW1ibGUgbWV0aG9kcwoKLSBXZSBjYW4gc2VlIHRoYXQgb3VyIGJhc2ljIG1vZGVsIHlpZWxkZWQgdmVyeSBnb29kIHBlcmZvcm1hbmNlIG9uIHVuc2VlbiBkYXRhLCBidXQgd2Ugd291bGQgbGlrZSB0byBwb2ludCBvdXQgdGhhdCBvdXIgZGF0YXNldCBpcyBxdWl0ZSBzbWFsbCBpbiBzaXplLCB0aGVyZWZvcmUgZW5zZW1ibGUgbWV0aG9kcyBsaWtlIHJhbmRvbSBmb3Jlc3RzIHdpbGwgbm90IHBlcmZvcm0gdmVyeSB3ZWxsIGFzIHRoZXkgbmVlZCBjb25zaWRlcmFibGUgYW1vdW50cyBvZiBkYXRhIGluIG9yZGVyIHRvIGJ1aWxkIG11bHRpcGxlIHRyZWVzIGFuZCBhcHBseSB0aGVpciBhbGdvcml0aG1zIHByb3Blcmx5LgotIFRoYXQgaXMgd2h5IGZvciBvdXIgc3BlY2lmaWMgcHJvYmxlbSB0aGVyZSBpcyBubyBuZWVkIHRvIGdvIHRvIHRoZXNlIGFkdmFuY2VkIHRyZWUtYmFzZWQgbWV0aG9kcy4gSG93ZXZlciwgd2UgY291bGQgc3RpbGwgbm90aWNlIGEgdmVyeSBnb29kIHBlcmZvcm1hbmNlIGZyb20gYm9vc3RpbmcgYW5kIGJhZ2dpbmcgbWV0aG9kcy4KLSBBbm90aGVyIG5vdGUgaXMgdGhhdCB0aGUgcmVzdWx0cyBhcmUgdmVyeSBkZXBlbmRlbnQgb24gdGhlIHRyYWluaW5nIHNldCwgbWVhbmluZyB0aGF0IHNtYWxsIGZsdWN0dWF0aW9ucyBpbiB0cmFpbiBhbmQgdGVzdCBhY2N1cmFjeSB3aWxsIGhhcHBlbiBpZiB3ZSBjaGFuZ2UgdGhlIHJhbmRvbSBzZWVkIHVzZWQgaW4gdGhlIHRyYWluLXRlc3Qgc3BsaXQgZm9yIGluc3RhbmNlLiBUaGlzIGhhcyBoYXBwZW5lZCBkdXJpbmcgb3VyIGV4cGVyaW1lbnRzIGFzIHRoZXkgcmVzdWx0ZWQgaW4gZGlmZmVyZW50IHZhbHVlcyBvbiBkaWZmZXJlbnQgbGFwdG9wcyBhbmQgZm9yIGRpZmZlcmVudCB2YWx1ZXMgb2YgdGhlIHNlZWQuCgojIyMgU1ZNCgpTdXBwb3J0IFZlY3RvciBNYWNoaW5lIGlzIGEgZGlzY3JpbWluYXRpdmUgY2xhc3NpZmllciB0aGF0IGNsYXNzaWZpZXMgb2JzZXJhdGlvbnMgdXNpbmcgYSBoeXBlcnBsYW5lIHRoYXQgYmVzdCBkaWZmZXJlbnRpYXRlcyBiZXR3ZWVuIHRoZSBjbGFzc2VzLiBJdHMgYWR2YW50YWdlcyBsYXkgaW4gdGhlIGZhY3QgdGhhdCB0aGV5IGFyZSB2ZXJ5IGZsZXhpYmxlIGFuZCB3b3JrIHdlbGwgb24gaGlnaC1kaW1lbnNpb25hbCBkYXRhLgoKV2Ugd2lsbCB1c2UgU1ZNIG9uIG91ciBkYXRhc2V0IHRvIGRlbW9uc3RyYXRlIGl0cyBjYXBhYmlsaXRpZXMuCgojIyMjIFRyYWluaW5nIHRoZSBtb2RlbAoKVGhlIGdvYWwgb2YgdGhlIFNWTSBpcyB0byBpZGVudGlmeSBhIGJvdW5kYXJ5IHRoYXQgbWluaW1pemVzIHRoZSB0b3RhbCBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBoeXBlci1wbGFuZSBhbmQgdGhlIGNsb3Nlc3QgcG9pbnRzIG9uIGVhY2ggY2xhc3MuCgpUaGVyZSBhcmUgdHdvIGh5cGVyLXBhcmFtZXRlcnMgdG8gdGFrZSBpbnRvIGNvbnNpZGVyYXRpb24gYmVmb3JlIHRyYWluaW5nIG91ciBTVk0gbW9kZWw6IGZpcnN0LCB0aGUgY29zdCAqQyogd2hpY2ggYWN0cyBhcyBhIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlciBhbmQgdHJhZGVzIG9mZiBjb3JyZWN0IGNsYXNzaWZpY2F0aW9ucyBvZiB0aGUgdHJhaW5pbmcgZXhhbXBsZXMgYWdhaW5zdCB0aGUgbWF4aW1pemF0aW9uIG9mIHRoZSBkZWNpc2lvbiBib3VuZGFyeS4gSW4gb3RoZXIgd29yZHMsIHRoZSBncmVhdGVyIHRoZSB2YWx1ZSBvZiAqQyogdGhlIGhpZ2hlciB0aGUgbnVtYmVyIG9mIGVycm9ycyBvY2N1cnJpbmcgaW4gdGhlIHRyYWluaW5nIGNsYXNzaWZpY2F0aW9ucy4gVGhlIHNlY29uZCBoeXBlci1wYXJhbWV0ZXIgKmdhbW1hKiBkZWZpbmVzIGhvdyBtdWNoIGN1cnZhdHVyZSB3ZSB3YW50IGluIG91ciBkZWNpc2lvbiBib3VuZGFyeS4gCgpXZSBzdGFydCBieSB0dW5pbmcgb3VyIG1vZGVsIGFjY29yZGluZyB0byBkaWZmZXJlbnQgdmFsdWVzIG9mICpnYW1tYSogYW5kICpDKi4gV2Ugd2lsbCBzdGFydCBieSB1c2luZyBhIGxpbmVhciBrZXJuZWwuCgpUbyB1c2UgdGhlIGNyb3NzIHZhbGlkYXRpb24gZnVuY3Rpb25zIGZyb20gdGhlIFtDYXJldF0oaHR0cHM6Ly90b3BlcG8uZ2l0aHViLmlvL2NhcmV0L3RyYWluLW1vZGVscy1ieS10YWcuaHRtbCNsMS1yZWd1bGFyaXphdGlvbikgcGFja2FnZSwgd2UgbmVlZCB0byB0dXJuIHRoZSAwLzEgY2F0ZWdvcmljYWwgdmFsdWVzIG9mIHRoZSB2YXJpYWJsZSAqQ2xhc3NlcyogaW50byAiZmlyZSIvIm5vdF9maXJlIiAoYXMgcmVxdWlyZWQpLiBUaGUgZnVuY3Rpb25zIHByb3ZpZGVkIHdpbGwgYWxsb3cgdXMgdG8gZmluZCB0aGUgYmVzdCB2YWx1ZXMgZm9yIGJvdGggKmdhbW1hKiBhbmQgKkMqLiB3ZSB1c2VkIGEgdHVuaW5nIGxlbmd0aCBvZiAxMC4KCmBgYHtyfQoKdHJhaW5fc2V0X3N2bSA8LSB0cmFpbl9zZXQKdGVzdF9zZXRfc3ZtIDwtIHRlc3Rfc2V0Cgp0cmFpbl9zZXRfc3ZtJENsYXNzZXMgPC0gbWFwdmFsdWVzKHRyYWluX3NldCRDbGFzc2VzLCBmcm9tPWMoMCwxKSwgdG89Yygibm90X2ZpcmUiLCJmaXJlIikpCnRlc3Rfc2V0X3N2bSRDbGFzc2VzIDwtIG1hcHZhbHVlcyh0ZXN0X3NldCRDbGFzc2VzLCBmcm9tPWMoMCwxKSwgdG89Yygibm90X2ZpcmUiLCJmaXJlIikpCmBgYAoKV2UgcGVyZm9ybSBjcm9zcyB2YWxpZGF0aW9uIHJlcGVhdGVkIDUgdGltZXMuCgoKYGBge3J9CmNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gInJlcGVhdGVkY3YiLCByZXBlYXRzPTUsIHN1bW1hcnlGdW5jdGlvbj10d29DbGFzc1N1bW1hcnksIGNsYXNzUHJvYnM9VFJVRSkKCnN2bV9tb2RlbF9yYWRpYWwgPC0gdHJhaW4oQ2xhc3NlcyB+IFRlbXBlcmF0dXJlK1JhaW4rRkZNQytETUMrREMrSVNJK0JVSStGV0krUkgsIGRhdGEgPXRyYWluX3NldF9zdm0sIG1ldGhvZD0ic3ZtUmFkaWFsIiwgdHVuZUxlbmd0aD0xMCwgbWV0cmljPSJST0MiLCB0ckNvbnRyb2w9Y29udHJvbCkKCnN2bV9tb2RlbF9saW5lYXIgPC0gdHJhaW4oQ2xhc3NlcyB+IFRlbXBlcmF0dXJlK1JhaW4rRkZNQytETUMrREMrSVNJK0JVSStGV0krUkgsIGRhdGEgPXRyYWluX3NldF9zdm0sIG1ldGhvZD0ic3ZtTGluZWFyIiwgdHVuZUxlbmd0aD0xMCwgbWV0cmljPSJST0MiLCB0ckNvbnRyb2w9Y29udHJvbCkKCnN2bV9tb2RlbF9yYWRpYWwKc3ZtX21vZGVsX2xpbmVhcgpgYGAKCgpVc2luZyB0aGUgcmFkaWFsIGtlcm5lbCByZXN1bHRlZCBpbiBhbiAqQVVDKiBvZiA5OSUgd2hpbGUgdGhlIGxpbmVhciBnYXZlIHVzIGFuICpBVUMqIG9mIDk5LjglLgoKV2Ugd2lsbCB0aGVuIHByb2NlZWQgdG8gc2hvdyB0aGUgY29uZnVzaW9uIG1hdHJpeCBvZiB0aGUgbW9kZWwuCgpgYGB7cn0KcHJlZHNfc3ZtX3RyYWluX2xpbmVhciA8LSBwcmVkaWN0KHN2bV9tb2RlbF9saW5lYXIsIHRyYWluX3NldF9zdm0pCnByZWRzX3N2bV90cmFpbl9yYWRpYWwgPC0gcHJlZGljdChzdm1fbW9kZWxfcmFkaWFsLCB0cmFpbl9zZXRfc3ZtKQogIApjb25mdXNpb25NYXRyaXgocHJlZHNfc3ZtX3RyYWluX2xpbmVhcix0cmFpbl9zZXRfc3ZtJENsYXNzZXMsIHBvc2l0aXZlPSJmaXJlIikKY29uZnVzaW9uTWF0cml4KHByZWRzX3N2bV90cmFpbl9yYWRpYWwsdHJhaW5fc2V0X3N2bSRDbGFzc2VzLCBwb3NpdGl2ZT0iZmlyZSIpCgpwcmVkc19zdm1fdGVzdF9saW5lYXIgPC0gcHJlZGljdChzdm1fbW9kZWxfbGluZWFyLCB0ZXN0X3NldF9zdm0pCnByZWRzX3N2bV90ZXN0X3JhZGlhbCA8LSBwcmVkaWN0KHN2bV9tb2RlbF9yYWRpYWwsIHRlc3Rfc2V0X3N2bSkKCmNvbmZ1c2lvbk1hdHJpeChwcmVkc19zdm1fdGVzdF9saW5lYXIsdGVzdF9zZXRfc3ZtJENsYXNzZXMsIHBvc2l0aXZlPSJmaXJlIikKY29uZnVzaW9uTWF0cml4KHByZWRzX3N2bV90ZXN0X3JhZGlhbCx0ZXN0X3NldF9zdm0kQ2xhc3NlcywgcG9zaXRpdmU9ImZpcmUiKQpgYGAKCkZvciB0aGUgcmFkaWFsIGtlcm5lbDogb3VyIG1vZGVsIGdhdmUgdXMgYW4gYWNjdXJhY3kgb2YgMTAwJSBvbiB0aGUgdHJhaW5pbmcgc2V0IGFuZCA5NC4yMyUgb24gdGhlIHRlc3Qgc2V0LgoKRm9yIHRoZSBsaW5lYXIga2VybmVsOiB0aGUgbW9kZWwgZ2F2ZSBhbiBhY2N1cmFjeSBvZiA5OC45NSUgb24gdGhlIHRyYWluIHNldCBhbmQgcGVyZm9ybWVkIHdvcnNlIHRoYW4gdGhlIHJhZGlhbCBrZXJuZWwsIHdoaWNoIGlzIGV4cGVjdGVkIHNpbmNlIGl0IGlzIGxlc3MgZmxleGlibGUgYW5kIGZpdHMgdGhlIGRhdGEgbGVzcyB3ZWxsLiBPbiB0aGUgdGVzdCBzZXQgaG93ZXZlciwgaXQgZ2F2ZSB1cyBhbiBhY2N1cmFjeSBvZiA5Ni4xNSUuIAoKT3ZlcmFsbCwgdGhlIGxpbmVhciBrZXJuZWwgZGlkIGEgYmV0dGVyIGpvYiBhdCBnZW5lcmFsaXppbmcgb24gdW5zZWVuIGRhdGEsIHdoaWNoIGlzIHdoeSB3ZSB3aWxsIGdvIGZvciBpdCBpbiBvdXIgbW9kZWwuCgojIyMjIFBsb3R0aW5nIHRoZSBST0MgY3VydmUKClRoZSBtb2RlbCB3aXRoIHRoZSBsaW5lYXIga2VybmVsIGdhdmUgdXMgYW4gQVVDIG9mIDk2LjEwJQoKYGBge3J9CgpST0NQcmVkIDwtIHByZWRpY3Rpb24oYXMubnVtZXJpYyhwcmVkc19zdm1fdGVzdF9saW5lYXIpLCBhcy5udW1lcmljKHRlc3Rfc2V0X3N2bSRDbGFzc2VzKSkKUk9DUGVyIDwtIHBlcmZvcm1hbmNlKFJPQ1ByZWQsIG1lYXN1cmU9InRwciIseC5tZWFzdXJlPSJmcHIiKQphdWMgPC0gcGVyZm9ybWFuY2UoUk9DUHJlZCwgbWVhc3VyZSA9ICJhdWMiKQphdWMgPC0gYXVjQHkudmFsdWVzW1sxXV0KYXVjIApwbG90KFJPQ1BlciwgY29sb3JpemUgPSBUUlVFKQoKYGBgCgoKCgoKCg==